1. 逆向工程中的花指令对抗技术
在移动安全逆向分析领域,unidbg作为动态二进制插桩框架已经成为分析加固SO文件的重要工具。但高级加固方案往往会采用花指令(Junk Code)这种反调试手段,通过在正常指令流中插入无效字节序列,干扰逆向工具的静态分析能力。最近我在分析某金融类APP的签名算法时,就遇到了典型的花指令干扰场景。
当unidbg执行到特定地址时,IDA反汇编视图出现大量无效跳转和破碎指令,常规的F5反编译直接失效。这种对抗技术本质上是通过插入不会被执行到的垃圾字节(如0xEB 0x01这种跳转1字节的短跳),打乱反汇编引擎的指令对齐判断。下面分享一套经过实战验证的花指令处理方案。
2. 花指令类型识别与特征分析
2.1 常见花指令模式
在ARM/Thumb架构下,主流的花指令实现方式包括:
- 无效跳转型:如
B #0x4跳转到下条指令中间插入垃圾字节 - 冗余运算型:
MOV R0, R0等不影响寄存器的无效操作 - 条件永真型:
CMP R0, R0+BEQ的永远成立分支 - 数据伪装型:将代码段伪装成数据段(如插入
.byte 0x12)
2.2 动态识别技巧
通过unidbg的console.debugger模式单步执行时,可以观察到:
- PC寄存器会跳过花指令直接到达有效代码
- 内存读写监控显示花指令区域无实际访问
- 通过
unidbg -D输出的指令执行日志可见无效指令序列
例如某案例中的典型模式:
armasm复制0x7F45: B #0x4 ; 跳转到0x7F4B
0x7F47: .byte 0xDE 0xAD ; 干扰反汇编的垃圾字节
0x7F4B: LDR R0, [R1] ; 实际有效指令
3. 基于unidbg的自动化处理方案
3.1 指令流清洗模块
在unidbg中扩展AbstractARM64Emulator类,重写内存读取逻辑:
java复制public class CleanEmulator extends AbstractARM64Emulator {
@Override
public byte[] mem_read(long address, int size) {
byte[] origin = super.mem_read(address, size);
if(isJunkCodeArea(address)) {
return cleanJunkBytes(origin);
}
return origin;
}
private byte[] cleanJunkBytes(byte[] code) {
// 实现基于模式匹配的字节替换
Pattern jumpPattern = Pattern.compile("\\xEB\\x01.{2}");
Matcher m = jumpPattern.matcher(new String(code));
return m.replaceAll("\\x90\\x90\\x90\\x90").getBytes();
}
}
3.2 动态Hook方案
对于无法静态清洗的复杂花指令,采用运行时Hook:
java复制emulator.getBackend().hook_add_new(
new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
if(address == JUNK_CODE_START) {
backend.reg_write(Unicorn.UC_ARM64_REG_PC, REAL_CODE_START);
}
}
},
JUNK_CODE_START, JUNK_CODE_START, null
);
4. 实战问题排查记录
4.1 花指令导致的寄存器污染
某次分析中遇到R0寄存器被花指令意外修改的情况,解决方案:
- 在花指令入口处保存寄存器上下文
- 跳转到清洁区域执行
- 恢复原始寄存器状态
java复制emulator.getBackend().hook_add_new(
new BlockHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
if(address == DIRTY_BLOCK) {
long r0 = backend.reg_read(UC_ARM64_REG_R0);
backend.reg_write(UC_ARM64_REG_PC, CLEAN_BLOCK);
backend.reg_write(UC_ARM64_REG_R0, r0);
}
}
},
DIRTY_BLOCK, DIRTY_BLOCK, null
);
4.2 反反调试技巧
部分加固会检测unidbg特征,解决方案:
- 修改
ro.product.model等系统属性 - Hook
libc.so的fopen等函数返回假数据 - 随机化内存加载基址(
emulator.set("base_addr", randomAddr))
5. 进阶对抗方案
5.1 控制流平坦化处理
对于结合控制流平坦化(CFG Flattening)的花指令:
- 使用
unidbg-android的FridaBridge模块注入frida脚本 - 通过
Interceptor.attach动态记录真实跳转目标 - 生成等效的clean CFG图
javascript复制Interceptor.attach(targetAddress, {
onLeave: function(retval) {
console.log(`Real jump to ${retval}`);
emulator.redirectJump(retval);
}
});
5.2 多引擎交叉验证
结合多种工具的优势:
- 用Ghidra的
Pcode分析数据流 - 用IDA的
microcode优化中间表示 - 最终通过unidbg动态执行验证
关键提示:处理花指令后务必验证原始算法逻辑是否改变,可通过比对输入输出哈希值确认