自己写了个简单的helloword.apk,用x固(二代)的版本加壳后,在模拟器能正常运行,今天来试试手动dump脱壳;用jadx打开加壳后的apk,发现结构我没加壳的比变化较大。该从哪入手分析了?整个apk中,AndroidMainfest是大而全的配置文件,标明了apk的四大组件、包明、入口点等所有配置信息,这里是不能混淆的,逆向必须从这里开始。从该文件查到:入口改成了TxAppEntry,是个application!
进入该Application中,代码如下所示:
attachBaseContext比onCreate先执行,所以这里的入口函数应该是attachBaseContext;attachBaseContext从log打印的字符串就能看出这是壳解密的开始;前面几行代码从字面看是奔溃日志采集和报告,最重要的是调用a(this)函数,所以先在进入a()函数继续分析:从log看是开始安装啥(应该是解密、脱壳了),调用了d()和load()函数;d()函数是拼接so库路径、然后调用System.load加载so库的代码逻辑很清晰了;接着就是load函数了;这个是native函数,既然放so层,肯定“见不得人“,可以试试从这里开始突破!那么这个load在那个so了? 从文件来看,只有3个so,shella或shellx最像,可以从这里下手;从名字看,x感觉是x86版本的,a感觉是arm版本的,所以从shella开始下手逆向分析;先用IDA打开:打开过程中IDA不停地报错,打开后右边函数列表没有JNI_OnLoad,也没有java开头的函数;只是在string这里找到了JNI_OnLoad字符串,直观感觉是动态注册,但JNI_OnLoad函数又找不到.........
导入和导出函数这里也是空的:
很明显这个so被处理过了,那么该从哪下手了?IDA解析失败,这里我们换010editor试试,用ELFTemplate试试:符号表都是0!
section table大部分也是0:
既然section大部分信息都是空的,干脆不让IDA按照section解析了,这里把section在文件的偏移、table entry size、table entry count、string table index都置0,如下:
奇迹发生了:导入导出表能看到部分函数了;左侧的函数列表也能看到部分函数了;JNI_OnLoad也能找到了:
不过这里的代码明显不正常:所以这里又需要注意一点:在JNI_OnLoad(或dex)执行之前肯定会解密的(好像又是一句真确的废话......)!
既然静态分析没啥结果,分析不出啥,也找不到脱壳的突破点,只能动态调试了;该apk自身+系统有几十个so,自己so的jni_onload方法又被加密了,该在哪下断点开始调试了?因为so是由androud的linker加载的,那就在linker这里下断点呗,这里只要抓住一点就行:肯定会先解密jni_onload!先找到linker下个断点:
然后单步调试,发现这里有大量的EOR异或指令,稍微优点经验的同学都能看出来这是在解密(windows下PG保护的代码也是异或加密的,执行的时候才用异或解密);而且这个方法被反复调用,说明代码在使用前才开始解密:
继续执行,同时看DDMS,发现日志已经打印了end install、attach end等关键字眼。还记得文章刚开始截图的解释么:那上面有源代码的分析,其中就有Log.d打印日志(有点像电影中“反派死于话多”的经典场景,想不通为啥x固的研发同学要打印这么多日志);已经执行到attch end,说明dex已经被完全解密并且加载到内存,下一步就是从内存dump dex了!
还有关键的dex的偏移和大小:这里是10进制,687124=0xA7C14,记住这个数,后面还有用(这里先打个伏笔)!
这里遇到了反调试的函数,直接换成00干掉(试了很多次,每次到这里就崩掉):
然后在jni_onload下断点,目的是找到动态注册的load函数:下断后单步调试,这里就是registerNative函数了:从参数可以看出来,动态注册了5个方法,R2就是映射数组的地址了:
继续执行:这里面就是dex的解密函数了(里面有大量的异或运算,很明显是解密用的)!
看到dex.035了吧?这就是dex文件头,内存这里的数据大概率是解密后的dex文件:大小是0A7C14=687124byte;和上面看到的DDMS打印出来的日志是不是一样的呀?
导出来,保存成dex文件,然后用jadx打开如下:MainActivity被复原:
总结:原始APK被加密的方式有很多种,但是无论怎么加密,在执行时肯定会解密!所以必须要动态调试,从linker下手,一步一步调试,指导找到解密后的so或dex;
- 怎么识别解密函数了?有大量异或的代码很可疑!比如在init_array中,有大量如下代码:都是通过循环,逐字符异或,这个字符串普通人是看不懂含义的,这里明显在解密!
- 怎么在内存找dex或so了?看文件头,dex是dex.035,so是elf!
基于此案例,总结一下加壳的一些要点:
小tips:
(1)我调试用的还是模拟器,不过不是常见的雷电、夜神等x86架构的,而是android stuido自带的arm模拟器,虽然速度慢很多,但是相比x86模拟器少了很多莫名其妙的bug!
(2)我用的低版本模拟器,android自身的防护功能少,更利于逆向和调试
参考:
1、https://blog.csdn.net/long117long/article/details/66477562 Android系统中的Application和四大组件一些方法的启动顺序
2、https://blog.csdn.net/tabactivity/article/details/106994496 Android SO 加壳(加密)与脱壳思路
3、https://blog.csdn.net/feibabeibei_beibei/article/details/75734795 乐固加壳的原理分析