记录一次某Android APP反编译获取源码、请求抓包、激活成功教程请求加密算法、使用python模拟请求实现登录的逆向过程,仅说明逆向思路和过程,APP信息和内容不会公开。
步骤1. 尝试apk反编译
下载要反编译的apk后,执行” apktool d app.apk”反编译,发现抱错如下:
I: Using Apktool 2.5.0 on app.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
Exception in thread "main" brut.androlib.err.RawXmlEncounteredException: Could not decode XML
at brut.androlib.res.decoder.XmlPullStreamDecoder.decode(XmlPullStreamDecoder.java:149)
at brut.androlib.res.decoder.XmlPullStreamDecoder.decodeManifest(XmlPullStreamDecoder.java:155)
at brut.androlib.res.decoder.ResFileDecoder.decodeManifest(ResFileDecoder.java:162)
at brut.androlib.res.AndrolibResources.decodeManifestWithResources(AndrolibResources.java:204)
at brut.androlib.Androlib.decodeManifestWithResources(Androlib.java:134)
at brut.androlib.ApkDecoder.decode(ApkDecoder.java:122)
at brut.apktool.Main.cmdDecode(Main.java:179)
at brut.apktool.Main.main(Main.java:82)
Caused by: java.io.IOException: Invalid chunk type (121).
at brut.androlib.res.decoder.AXmlResourceParser.doNext(AXmlResourceParser.java:906)
at brut.androlib.res.decoder.AXmlResourceParser.next(AXmlResourceParser.java:102)
at brut.androlib.res.decoder.AXmlResourceParser.nextToken(AXmlResourceParser.java:112)
at org.xmlpull.v1.wrapper.classic.XmlPullParserDelegate.nextToken(XmlPullParserDelegate.java:105)
at brut.androlib.res.decoder.XmlPullStreamDecoder.decode(XmlPullStreamDecoder.java:142)
去github上查看使用的版本为2.5.0的apktool源码,找到报错代码:
if (chunkType < CHUNK_XML_FIRST || chunkType > CHUNK_XML_LAST) {
throw new IOException("Invalid chunk type (" + chunkType + ").");
}
猜测由于加固修改了AndroidManifest.xml内容造成chunkType异常,直接将apk修改后缀.zip解压,得到编译后的AndroidManifest.xml,使用010 editor打开该文件,去010 editor模板下载页面下载 AndroidManifest.bt模板,导入后运行该模板:
发现多出了些多余字节,删除之后保存执行java -jar AXMLPrinter2.jar AndroidManifest.xml > ok.xml
成功,可正常反编译AndroidManifest.xml。
将删除多余字节的AndroidManifest.xml替换到apk中,重新使用apktool反编译,成功的得到了AndroidManifest.xml、资源文件以及smali字节码文件。
利用这些反编译文件可以做一些简单分析,还可以用来实现smali插桩、修改SSL证书等功能。
步骤2. 查看加壳代码
将上面得到的smali字节码利用smali.jar转为dex,或者直接解压apk得到dex,使用jadx-gui反编译得到java源码,查看只有几个类,确认该apk已加固。
查看代码发现有Application子类,通过查看AndroidManifest.xml的application-android:name标签确认使用该自定义Application,代码如下:
通过一些类、方法命名可知代码已经过混淆,attachBaseContext先于onCreate最先执行,查看C0009a.m34a方法如下:
程序刚启动时会把使用的一些文件,包括.so文件拷贝到dataDir下,并且加载了该动态库。
在手机中安装apk,直接运行,在目录”/data/data/包名”下可以找到这个ELF文件,通过”adb pull”命令拉取到本地电脑上。
之所以在手机中获取,而不是直接拷贝下lib下的so文件,是因为程序加固修改了lib下的so文件,而”/data/data/包名”下的so是可通过”System.loadLibrary”直接加载,格式正确的so文件。
步骤3. 分析加壳过程
- attachBaseContex
- 将加密的dex文件从assets里读取出来,保存到dataDir下
- 使用自定义的DexClassLoader加载解密后的原dex文件
- onCreate
- 反射修改ActivityThread类,并将Application指向原dex文件中的Application
- 创建原Application对象,并调用原Application的onCreate方法启动原程序
通过加壳包名、加壳代码、so文件命名等可知,这个APP的加壳方式并不是市场常见的加壳方式。
步骤4. 脱壳环境准备
无论是Frida脱壳还是Xposed脱壳,都需要一台root的手机。为了方便直接使用VMOS创建一个已root的虚拟机系统,创建完毕安装此APP,安装后点击运行直接闪退,通过adb连接查看日志,发现报错”不可在模拟器上运行!”,发现应用做了模拟器检测,并且不知道具体检测手段不好绕过,直接采用真机。
使用的手机是一台小米 8,系统是Android 10.0,小米手机解锁Bootloader比较方便,手机绑定小米账号后,下载小米手机解锁工具解锁bl,解锁后通过安装Magisk实现手机root。
root后安装此应用,点击运行直接闪退,通过adb连接查看日志,发现报错”手机已root!”,应用也做了root检测,为了让app正常运行起来,首先要绕过app的root检测。
下载magisk模块EdXposed完整框架并安装,安装后EdXposed安装RootCloak模块,并且在magisk设置中打开MagiskHide,重命名magisk包名,完成后重启,点击应用运行已经能够正常运行起来。
为了后续脱壳方便,magisk安装adb_root模块,让adb可以通过adb root
获取到root权限。
步骤5. 应用脱壳
一般脱壳首先要从内存中dump出dex:
-
hook法:Android 5.0~8.0版本可以用hook libart.so中的OpenMemory方法,8.0及8.0以上可以用hook libdexfile.so中的OpenCommon方法(OpenCommon源码),Android 10.0的libdexfile位于/apex/com.android.runtime/lib/libdexfile.so,通过获取到dex的内存地址和大小,然后从内存中导出。
-
特征匹配法:dex的header中magic魔数固定”64 65 78 0a 30 ?? ?? 00″,可用魔数和其他dex的特征皮匹配,从内存中直接搜索找到dex地址,再通过dex的header中的file_size获取dex文件大小,hluwa/FRIDA-DEXDump就是这种实现方法。
脱壳的技术还有很多,感兴趣可以查看Android逆向之逆向工具 中整理的脱壳工具,这里直接采用hluwa/FRIDA-DEXDump将dex从内存中导出,导出后使用jadx将导出的dex导出为gradle项目,获取得到源码导入Android studio。
步骤6. 请求抓包
手机安装Charles的https证书之后,使用charles抓包APP发现无法抓包,使用Drony + Charles使用 VPN 导流后进行抓包成功,直接采用HttpCanary安装https证书后也可以抓包成功。防止被抓包的方式有多种,比如okhttp禁用代理等方式,可以hook相关代码绕过。抓包后导出:
步骤7. 请求解密
查看项目源码,项目是基于okhttp+rxjava+retrofit的MVP架构,猜测request加密和response解密代码位于”okhttp3.Interceptor”的实现类,找到了实现类,但是方法具体实现已经被抽取到外部:
继续查看代码,找到了sm2、sm3、sm4算法的实现类,算法介绍:
算法 | 类型 | 说明 |
---|---|---|
sm2 | 非对称加密 | 公钥密码算法,公钥64字节,私钥32字节,每次加密密文不同 |
sm3 | 摘要算法 | 杂凑算法,输出32字节 |
sm4 | 对称加密 | 分组对称密码算法,密钥16字节,有CBC和ECB两种模式 |
编写Xposed插件,hook sm2算法加密解密,sm3加密,sm4加密解密方法,可以从方法参数和返回值获取加密密钥、请求内容以及加密后的数据等信息,因为程序已经加壳,需要通过程序的Application的attachBaseContext方法获取真实classLoader:
public class CustomHook implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
String strPackageName = "com.sgcc.wsgw.cn";
XposedHelpers.findAndHookMethod("xx.xx.Application", lpparam.classLoader,
"attachBaseContext", Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Context context = (Context) param.args[0];
TruClassLoader classLoader = context.getClassLoader();
//hook sm2、sm3、sm4加解密代码
}
});
}
}
}
hook方法参数后可以看到加密前的请求内容,并推算出加解密方式后用python实现,模拟发送请求,测试成功:
步骤8. 抽取方法
因为APP很多核心实现方法都被抽取到外部,so代码做了混淆,是三代壳一种实现方式,虽然可以通过hook方法推算出请求加解密,很多实现逻辑还是看不到。
可以通过修改Android源码并刷机,在dex加载类之后,第一次调用之前做主动调用,壳这时会将dex method解密,获取dex的code_item之后写入dex便可以得到抽取的方法。
开源的脱壳技术如FART和AUPK就是使用类似方法实现三代壳脱壳的。
总结
APP使用了模拟器检测,root检测,Xposed检测,代码混淆,so代码混淆,dex method抽取动态解密,消息sm2对称加密,消息sm4非对称加密,消息sm3防篡改等技术提高了安全性。但是root检测不难绕过,绕过之后可以直接在内存dump出dex,可以看到部分代码实现,通过hook函数推算出加密解密算法和密钥,也可以通过复杂写的三代壳脱壳方式得到完整代码。可以通过继续加强root检测,Frida检测,Magisk检测,针对FART等脱壳技术检测,SSL改为双向认证,sm4密钥设置规则等方式进一步提高安全性。
参考
今天的文章Android逆向之某APP逆向实践分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/20808.html