逆向工程侦探手记:拆解DroidGuard虚拟机的加密迷宫
那是一个普通的周二下午,咖啡杯里的液体已经凉透,而我正盯着IDA Pro里那段诡异的汇编代码发呆。作为一名常年与Android安全机制较劲的逆向工程师,Google的DroidGuard模块就像一座戒备森严的城堡,而今天,我决定从它的护城河开始探索。
1. 初探DroidGuard的防御工事
第一次接触DroidGuard是在分析某金融应用的安全机制时。这个隐藏在Google移动服务(GMS)中的组件,通过名为xssNative的JNI接口与Java层通信。在libdroidguard.so中,我发现了五个关键函数链:
sub_19BAC → sub_5B7FC → sub_1E1E4 → sub_1E430 → sub_1F9CC最有趣的是sub_1F9CC,它的控制流图像极了一个小型CPU的指令周期:
- 取指 → 解码 → 执行 → 更新PC
- 循环中穿插着内存访问检查
- 关键跳转前总会有信号检测逻辑
反调试陷阱很快给了我下马威。每当我在sub_1F9CC下断点,进程就会神秘退出。通过strace跟踪,发现它在接收SIGTRAP信号后自杀。解决方案是hooksigaction:
def hook_sigaction(libc, signum): if signum == 5: # SIGTRAP return 0 return original_sigaction(signum)2. 虚拟机的内存迷局
突破反调试后,一个奇怪的内存地址a1+5928引起了我的注意。这个区域在每次虚拟机启动时都会:
- 被
sub_57928清零 - 由
sub_59AE8填充加密数据 - 在
sub_19358中被重新分配
使用IDA的Watch脚本追踪发现,这里存放着256个加密虚拟寄存器。更精妙的是,虚拟机支持直接对加密寄存器进行运算:
| 寄存器特性 | 技术实现 |
|---|---|
| 加密存储 | 每个寄存器使用32字节加密结构 |
| 安全运算 | 指令集支持加密态算术操作 |
| 动态密钥 | 寄存器编号参与密钥生成 |
核心加密算法位于sub_1B304,其Python还原版如下:
def rw_register(register_num, data): v10 = 0x9ab484eb8c37f9a3 # 固定魔数 v11 = 0x6b9136c76d59d9fd # 动态变换值 # 密钥调度算法 v11 = ((((v11 | 1) ^ 0x3F) + (v11 & 0x3E)) & 0xFE | v11 & 1) ^ 1 v11 &= ((v11 & 0x10 & register_num | 1) + 2 * (v11 & 0x10 ^ register_num) + (v11 & 0x10 ^ register_num ^ 0x3F)) # 动态移位加密 shift = v11 % 64 rotated = (v10 << shift) | (~(v10 >> (64-shift))) return (data + ~(rotated & data) - (data | ~rotated)) & 0xFFFFFFFFFFFFFFFF3. 协议层的加密套娃
当跟踪到protobuf数据加密时,事情变得更有趣了。加密过程像俄罗斯套娃:
- 外层使用8字节加密表轮换
- 中层采用PCBC模式链式加密
- 内层是字节级的异或混淆
关键加密表生成算法如下:
def gen_encTable(cipher, cipher_num_p2): v133 = cipher & 0xFFFFFFFF v134 = cipher >> 32 for _ in range(32): # 32轮Feistel结构 # 轮函数处理低32位 k = const_list[v130 & 3] v134 = (v133 + ((16*v133 ^ (v133>>5)) & ~k)) + v134 # 轮函数处理高32位 k = const_list[(v130>>11) & 3] v133 = (v134 ^ ((16*v134 + (v134>>5)) ^ k)) + v133 return (v134 << 32) | v133最狡猾的是初始密钥的获取方式——它来自一个神秘的pcbc文件,路径形如:
/data/user/0/com.google.ads.rewardedvideoexample/app_pccache/5/43DD0D45399166CCF9057785EDF137EC7719BB95/pcbc通过hookjava.io.FileInputStream,发现这个文件有三大神奇特性:
- 同一设备重装GMS会生成不同文件
- 文件内容与设备硬件信息相关
- 包含22266字节的种子数据
4. 逆向工程的方法论沉淀
这场持续三周的技术侦探之旅,总结出几条实用经验:
动态分析优先:
- 先用Frida hook关键JNI接口
- 内存断点比代码断点更有效
- 日志注入要精确到指令级
逆向技巧组合:
- 对模糊代码使用符号执行
- 复杂算法用Python原型验证
- 关键路径用IDA Trace记录
工具链配置建议:
| 工具 | 用途 | 配置要点 |
|---|---|---|
| IDA Pro 7.7 | 静态分析 | 配置ARM64处理器扩展模块 |
| Frida 16.0 | 动态Hook | 禁用SIGSEGV处理 |
| Qiling | 指令级模拟 | 定制内存访问回调 |
记得在某个深夜,当我终于让还原的Python算法生成与so中相同的结果时,显示器上的十六进制数字仿佛在跳舞。这种快乐,大概就是逆向工程师的"颅内高潮"吧。下次当你面对DroidGuard这样的复杂系统时,不妨记住:所有加密迷宫,终究会为耐心的侦探留下面包屑。