[="]偏移 | 字节 | 描述 |
0 | 4 | 文件头标识,值固定(0x04034b50) |
4 | 2 | 解压文件所需 pkware最低 版本 |
6 | 2 | 通用位标记(最低位奇数代表加密,偶数代表未加密) |
8 | 2 | 压缩方法 |
10 | 2 | 文件最后修改时间(MS-DOS时间) |
12 | 2 | 文件最后修改日期(MS-DOS日期) |
14 | 4 | CRC-32校验码 |
18 | 4 | 压缩后的大小 |
22 | 4 | 未压缩的大小 |
26 | 2 | 文件名长度 |
28 | 2 | 扩展区长度(一般为 00 00) |
30 | n | 文件名 |
30+n | m | 扩展区(不一定存在) |
文件头后则是文件内容,而文件描述只有当文件头的通用标记字段的第3位设为1时才会出现,一般在磁盘上存储的ZIP文件都没有文件描述。
目录区
目录区记录了压缩包里所有的子目录的描述信息。表 2 目录区数据结构
偏移 | 字节 | 描述 |
0 | 4 | 目录文件头标识(固定值0x02014b50) |
4 | 2 | 压缩所用的pkware版本 |
6 | 2 | 解压所需pkware最低版本 |
8 | 2 | 通用位标记(最低位奇数代表加密,偶数代表未加密) |
10 | 2 | 压缩方法 |
12 | 2 | 文件最后修改时间(MS-DOS时间) |
14 | 2 | 文件最后修改日期(MS-DOS日期) |
16 | 2 | CRC-32校验码 |
20 | 4 | 压缩后的大小 |
24 | 4 | 未压缩的大小 |
28 | 2 | 文件名长度 |
30 | 2 | 扩展区长度 |
32 | 2 | 文件注释长度 |
34 | 2 | 文件开始位置的磁盘编号 |
36 | 2 | 内部文件属性 |
38 | 4 | 外部文件属性 |
42 | 4 | 文件头的相对位移 |
46 | n | 目录文件名 |
46+n | m | 扩展区 |
46+n+m | k | 文件注释内容 |
目录结束标记区
目录结束标记区用于标记压缩包目录数据的结束。表 3 目录结束标记区
偏移 | 字节 | 描述 |
0 | 4 | 目录结束标记(固定值0x06054b50) |
4 | 2 | 当前磁盘编号 |
6 | 2 | 目录区开始位置的磁盘编号 |
8 | 2 | 当前磁盘上记录的目录数 |
10 | 2 | 目录总数 |
12 | 4 | 目录区大小 |
16 | 4 | 目录区开始位置偏移 |
20 | 2 | 注释长度 |
22 | n | 注释内容 |
2、典型对抗样例
为了不影响APK的正常安装和运行,幕后开发者不可能修改APK文件(ZIP包)的任意内容,而只能修改某些不会被Android包安装服务检测的特征数据,包括通用标记字段的加密标记位、磁盘编号、目录总数等,修改的位置集中在APK文件的头部和尾部。
通用标记字段
修改通用标记字段后,解压APK会提示输入密码,而实际上APK肯定不可能存在密码,否则无法正常安装。
破解方法就是将字段改成“00 00”即可(要注意,通用标记字段同时存在于数据区和目录区)。
图 3 通用标记字段被修改
磁盘编号
修改磁盘编号后,解压文件会提示“头部错误”,这个错误利用的是ZIP文件的“过时缺陷”。ZIP格式诞生于MS-DOS时代,当时由于数据主要存储于软件和光盘,受容量限制,ZIP文件可能需要分盘存储,因此ZIP文件中要记录磁盘编号,因此当前磁盘编号和目录区开始位置磁盘编号可能不一致。而现在ZIP文件都存储在磁盘中,容量足够,无须分盘存储,因此当前磁盘编号和目录区开始位置磁盘编号一致(一般为0)。破解方法就是将当前磁盘编号和目录区开始位置磁盘编号字段都修改成“00 00”。
图 4 磁盘编号被修改目录数
目录数
类似于磁盘编号,ZIP文件也记录了当前磁盘记录的目录数以及目录总数,修改该字段后也会导致解压APK文件失败。破解方法就是将当前磁盘记录的目录数以及目录总数保持一致,如下图所示把“7777”修改成“3F 0A”即可:
图5 目录数被修改
二、基于AndroidManifest文件格式的反编译对抗
1、AndroidManifest文件格式
AndroidManifest文件(AndroidManifest.xml)是APK中的关键文件,记录了APP的包名、版本、权限信息、四大组件信息等信息。Android的包安装服务通过解析该文件才能正常的安装APK。AndroidManifest文件有文本和二进制两种形式。APP开发阶段,开发者通过文本形式的AndroidManifest文件明文配置APP的各项信息;APP打包后,各项信息则被编译工具编译成二进制数据存储于AndroidManifest二进制文件中。因此,未经反编译直接解压APK后,打开AndroidManifest文件,只能看到乱码。
AndroidManifest文件结构较复杂,一般可以借助看雪大神MindMac出品的AndroidManifest二进制文件结构图(图6)以及AndroidManifest模板(十六进制编辑器自带的010 Editor模板,图7)进行学习和研究:
图6 AndroidManifest二进制文件结构图
图7 010 Editor自带的 AndroidManifest模板
2、典型对抗样例
基于AndroidManifest文件格式的反编译对抗的原理和基于ZIP文件格式的反编译对抗原理是一样的,对AndroidManifest某些特征数据的修改可以“欺骗”反编译工具从而导致反编译失败,但Android的包安装服务对该类“欺骗”免疫而不会影响APP的正常安装。
幻数(MagicNumber)
AndroidManifest文件的幻数(Magic Number),即文件头为0x00080003。将其修改后,反编译工具就无法识别AndroidManifest文件,导致反编译失败。破解方法就是修复幻数,如下图,即需将“0000 08 00”修改成“03 00 08 00”:
图 8 幻数被修改
字符串个数
字符串个数是另一种典型的对抗,​不同于一眼就能看穿的对幻数的修改,对字符串个数的修改需要对整个字符串组块进行分析(主要分析字符串偏移区)。如下图所示,该APK实际包含的字符串个数为350个,但开发者明显修改了(分析红框区域的字符串编号和绿框区域的字符串编号异同以及蓝框区域的字符串编码)AndroidManifest文件中记录的字符串个数,从而会导致反编译失败,而破解的方法就是修复字符串个数:
图 9 字符串个数被修改综上所述,基于当前已获得的APK样本,笔者研究分析了基于APK文件格式的两种反编译对抗的原理以及其典型对抗样例,并总结了相应的破解方法。后续,笔者将持续跟踪此类反编译对抗机制,在已有成果上完善补充。