给aapt添加支持uincode类名功能
aapt 是什么?
aapt是Android App资源打包工具。
为什么有这个需求
- Android的开发语言Java用uincode字符作为类名、字段名和方法名。
- 现实中专业开发人员很少用uincode字符作为类名、字段名和方法名。
- aapt在开发时没有考虑做这种支持,但其实资源文件中和AndroidManifest.xml中会引用到java类名(Activity类,自定义View类等)。
- 一些商业代码混淆工具利用这个特性增加逆向工程的难度。如把类名混淆为不可见unicode字符,影响人类阅读分析,但不影响虚拟机执行。
- 资源文件和AndroidManifest.xml中的类也需要替换为混淆后的类名。这些商业工具在混淆的时候也对资源文件和AndroidManifest.xml进行了相应处理。
- Android逆向人员重新打包App时,需要使用到aapt工具,但aapt无法处理这种情况。
如何解决
问题的表象
直接执行aapt会得到下面的错误提示信息
1 | presets_main_layout.xml:7: error: Error parsing XML: not well-formed (invalid token) |
1 | <?xml version="1.0" encoding="utf-8"?> |
下面是资源文件presets_main_layout.xml的内容
问题的根源
阅读Android源码知道,aapt使用了expat XML解析库来解析资源xml文件,xmltoc.c文件里的utf8_isname2、 utf8_isname3两个函数用来判断utf-8字符是否为合法的xml元素名字字符。
我们要做的就是要patch这两个函数
下面以utf8_isname3函数为例讲解patch过程。
utf8_isname3函数内容
1 | static int PTRFASTCALL |
UTF8_GET_NAMING3是一个宏定义。内容如下:
1 | #define UTF8_GET_NAMING3(pages, byte) \ |
上面的代码显示utf8_isName3最终用到了两个全局变量namingBitmap和namePages。
这两个变量定义在nametab.h中。
两个全局变量的内容为:
1 | static const unsigned namingBitmap[] = { |
我们可以通过在exe中直接查找同时用到这两个变量的代码来定位函数所在位置。
在expat在解析xml时后, aapt还对类名做了一些检测:允许的类名字符为: “abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789$”
这里的判断逻辑功能存在于Resource.cpp中,Android9.0源码版本中共有4处判断。
下面是相关的代码片段。
1 | const char* classIdentChars = "abcdefghijklmnopqrstuvwxyz" |
补丁办法
- patch utf8_isname[23]函数,直接返回1.
- 修改相关validateAttr调用的返回值或删除相关对validateAttr调用。
为执行文件打补丁
这里我们使用apktool2.40.jar中的aapt_64.exe(jar包的prebuilt\windows目录中)为例。
apktool对aapt添加了一些功能,提供了windows、linux和macosx三个平台的版本。
逆向工作人员使用apktool居多。
- 用IDA的二进制查找功能查找namePages变量
得到地址0x56CF30
用同样的方法查找namingBitmap。注意namingBitmap中的值是4字节整数,在变为byte序列时需要倒序。
如我要以0x04000000, 0x87FFFFFE两个整数为特征值去查找,那么实际查找字节内容为:
00 00 00 04 FE FF FF 87
查找后得到地址:0x56C1F0
查找引用0x56C1F0的代码块,在附近查找对地址0x56CF30的引用。
如我们找到下面这一处(代码短小,对两个全局变量都有引用,可对照源码分析一下,会发现对“& 0x1F”相关指令被优化掉了,Android SDK中的版本是有相关指令的)。
代码结尾附近恰好有mov eax, 1指令,我们把后面改变eax寄存器值得指令”shl eax, cl”和“and eax, [rcx + dx * 4]” nop掉就可以了。
对找到的其他挤出(2-3)处相应修改, expat相关的代码就patch好了。
查找字符串“_0123456789$”(这里可以使用字符串查找功能),得到地址:0x55AEDD
查找相关引用:
跳转到对应代码:
因为代码优化的原因,这里并不像源代码中那样做了直接比较,而是把返回值保存在esi寄存器中了。
这里我们直接删除call调用的相关指令:直接用”mov, eax, 0FFFFFFFFh”替换”call sub_41FAD0”, 都是5字节指令,完美。
对其余4处做相应修改。
至此patch完毕,保存,然后替换apktool.jar中的相应文件即可。
其他选择
除了patch,也可以直接用源码进行编译。
- 可编译源码。aapt-apktool_v28
- apktool aapt源码版本:apktool aapt