论坛首页 逆向工程技术区 阅读主题

[原创]APP加固dex解密流程分析

278 浏览 23 回复
#1 楼主 2026-06-01 21:09:11
在去年9月的时候,我就想研究一下apk的加固,在网上逛了一圈,感觉有一个加固不错,所以先选它啦,写了一个测试apk丢到这个加固里面加固了一下这里我用的是这个加固的免费版(因为付费版太贵啦)本来计划着去年分析完的,但是总是抽不出一段完整的时间来所以就拖到了现在,终于最近因为过年赋闲在家,就花了几天分析了一下,感觉这几天探索加固壳的过程真是充满了惊喜和乐趣呢~2024年3月8号记:距离上次看这个加固已经半个月啦,今天正想着自己要做什么事情的时候,到博客看了眼TODOlist,突然发现咦我是不是还没看过dex的解密算法长什么样?所以今天就把这个给做完了,不得不说逆向真的是消磨无聊时间最有效的方法哈哈哈包名:com.oacia.apk_protect入口:com.stub.StubApp我们从AndroidManifest.xml中可以得知,这个加固的入口是com.stub.StubApp,所以我们就先进到apk的入口进行分析在这个入口类中,不仅有常规的onCreate()函数,还有一个函数值得注意,他就是attachBaseContext(Context context)Application 的onCreate和attachBaseContext是 Application 的两个回调方法,通常我们会在其中做一些初始化操作,attachBaseContext 在onCreate之前执行其中出现的字符串经过了加密混淆操作,加密函数如下,算法是将所有的字符与16进行异或我们写个jeb脚本把加密字符串解密来方便后续的静态分析解密后的效果如下我们往下进行分析,可以知道attachBaseContext的第一个作用是根据目标手机的架构加载libjiagu_xxx.so如图这些so在assets目录下 在加载完libjiagu_xxx.so之后,还调用了DtcLoader类进行初始化,这里使用的jadx的反编译结果,因为jeb没有反编译出DtcLoader.init();方法来DtcLoader类如图所示当DtcLoader类被加载到JVM中时,会去加载libjgdtc.so,如果加载失败,则会尝试从/data/app/com.oacia.apk_protect/lib/arm64/libjgdtc.so或者/data/data/com.oacia.apk_protect/lib/libjgdtc.so中去加载这个so但是当我们去进入到这两个目录进行寻找时,却发现没有这个libjgdtc.so存在所以我们的分析重点是在libjiagu.so中,这里我选取了arm64架构的so文件libjiagu_a64.so进行分析我们使用ida分析libjiagu_a64.so,发现导入表和导出表都没有内容,既然是这种情况,那么应该是在so装载进内存时导入导出表才会去进行相应的链接操作所以我们可以先用frida把这个so给dump下来首先我们在手机上运行一下frida server随后做一下端口转发frida命令行语句如下注入如下脚本随后我们使用SoFixer修复一下这个so,这里的-m参数即这个so在内存中的base基地址修复完成后,导入表和导出表就恢复了首先我们去hook一下打开so的函数日志如下,所以反调试是在libjiagu_64.so中然后去hook打开文件的函数open日志如下这里我们发现了/proc/self/maps,这是常见的反调试,要绕过这个检测,我们可以备份一个正常的maps文件,然后用frida去hook open函数,如果匹配到字符串maps,就将字符串重定向到我们备份的maps文件首先我们正常打开一次加壳的apk,然后使用下列命令备份maps然后我们注入如下frida脚本但是当注入这段脚本后,进程由于非法内存访问而退出了,这说明加固壳不仅读取maps文件,并且会尝试访问maps文件中所记录的文件或内存映射.这里由于frida注入后重启apk,但是备份的maps文件中记录的是先前的映射起始地址(这块内存在关闭apk后就被抹去了),所以当壳尝试访问其中的映射时产生了非法内存访问从而让进程崩溃这里我的解决方式是将上述frida代码中的fakePath赋值为一个不存在的文件例如/data/data/com.oacia.apk_protect/maps_nonexistent,来让壳没有内容可以访问修改完fakePath后重新注入代码,这个打印出来的日志可以说非常有意思,我们来看一下,相比hook open之前的日志,我们成功的让这个加固壳释放出了dex然而这个壳似乎是又发现了些什么异常,随后赶紧让app退出了,但是由于退出的太过仓促,它甚至还没有来得及把dex从文件夹中删除用010editor打开classes.dex,发现前几位并不是dex的魔术头,说明这个dex还没有被解密,不过现在我们只需要分析dex如何被壳从内存中释放出来的过程就可以了~如何可以定位到是什么位置调用了open函数来打开classes.dex呢?很简单,打印一下堆栈就可以了假如我们使用常规的frida打印堆栈代码,即使用DebugSymbol.fromAddress函数来判断地址所在的so的位置,那么进程是会报错退出的所以这里DebugSymbol.fromAddress所实现的逻辑需要自己编写,即下方的addr_in_so函数于是我们得到了释放三个dex文件的堆栈回溯这里我们发现classes.dex与classes2.dex的堆栈回溯完全相同,并且classes3.dex的前半部分和前两个dex的堆栈一样,随后进程便又退出了通过对堆栈的分析,我们可以发现三个dex应该是在一个循环中被依次加载的接下来我们便跳转到堆栈所打印的偏移来进一步分析下然而当我们跳转到堆栈回溯中的libjiagu_64.so的偏移0x19b780或者0x134598时,却发现这些地址的值都是0我们很快就能想到这里用到的技术应该是先将一块内存标记为可写可执行,随后将字节码填充进去,所以说,我们只需要在壳打开dex时,将此时的libjiagu_64.so从内存中dump下来就可以了然后再去用SoFixer修复这个dump下来的so再次来到偏移0x19B780处,可以发现这块空内存已经被填充了数据接下来我们想知道的是究竟是从什么地方开始被填充了新的数据,所以我们可以用WinMerge来让填充和未填充数据的so进行比较看看,结果却有了惊人的发现,被填充的数据是从0xe7000开始的,它的开头竟然是ELF文件的魔数头!?这有意思了,那么就是一个so里面藏了另外一个so咯~我们写个python脚本,把这个ELF从0x0e7000开始后面的所有字节都复制到新的文件里面但是当把这个elf提取出来之后拿010editor看却发现program header table被加密了这就导致ida根本就无法进行正常的分析壳elf加载主elf,并且program header还被加密了,感觉这种形式很像是 自实现linker加固so对于这种加固方式,壳elf在代码中自己实现了解析ELF文件的函数,并将解析结果赋值到soinfo结构体中,随后调用dlopen进行手动加载来到ida里

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-280609.htm
#17 2026-06-01 21:09:11
360加固是业界最强的了。一个免费版就这么折腾!
#18 2026-06-01 21:09:11
脱壳哪用那么麻烦。主要是修复VMP。看起来内容挺多,但是有点太多
#19 2026-06-01 21:09:11
niu
#20 2026-06-01 21:09:11
感谢分享
#21 2026-06-01 21:09:11
太牛了
#22 2026-06-01 21:09:11
真强啊;写的好像也挺详细的,只是我只看懂第一章节。。。
#23 2026-06-01 21:09:11
#24 2026-06-01 21:09:11
大佬真的猛……结构体就收下啦!谢谢!
‹ 上一页 1 2 下一页 ›

请登录后参与讨论

立即登录 注册账号