所谓对抗反汇编技术,就是在程序中使用特殊构造的代码或数据,让反汇编工具产生不正确的指令。恶意代码使用该技术,可以一定程度上阻碍相似性检测算法和启发式反病毒检测。
一、反汇编算法的局限性
反汇编软件在拿到一堆机器码时,采用什么样的思维和算法来“断词断句”,又基于哪些假设,都会决定最终的解析结果。运用不同的反汇编器,同样的字节码会被“翻译”出完全不同的指令序列。而对抗反汇编技术就是利用了反汇编器算法的天生漏洞来实现的。现有的反汇编算法有两种,各自皆有不同的局限性。
1、线性反汇编
线性反汇编容易实现,也最容易出错。来看它的算法原理:
char buffer[BUF_SIZE]
int position=0;
while(position<BUF_SIZE){
x86_insn_t insn;
int size=x86_disasm(buf,BUF_SIZE,0,position,&insn);
if(size!=0){
char disassembly_line[1024];
x86_format_insn(&insn,disassembly_line,1024,intel_syntax);
printf("%s\n",disassembly_line);
position += size;
}else{
//invalid(unrecognized instruction)
position++;
}
}
可以看出,该算法优先将字节码翻译成指令。然而二进制文件中,既有代码,也有数据。恶意代码编写者就可以利用这个漏洞,植入能够组成多字节指令机器码的数据字节。这样,无关的一些数据就会被“翻译”成call指令、jmp指令等等,干扰分析。
2、面向代码流反汇编
IDApro等软件运用的就是这种更先进的反汇编算法。它并不盲目地反汇编整个缓冲区,也不假设仅有指令而无数据,相反,它会随着代码逻辑,边走边“翻译”,建立一个需要反汇编的地址队列。
然而即便如此,它也有自身的局限性。例如,条件分支中,它优先处理false分支,往往会把作者有意植入的一些数据字节“翻译”成指令,产生错误结果。
当IDA反汇编出错时,需要我们手动将数据和指令互相转换。C键可以将光标处的数据转换成代码,D键可以将光标处代码转换成数据。
二、对抗反汇编技术
1、相同目标的跳转指令
jz loc_512之后紧接着jnz loc_512,等价于jmp指令。然而反汇编器每次只反汇编一条指令,所以意识不到这一点,仍然继续反汇编jnz的false分支。例如:
IDA反汇编出来的错误结果:
74 03 jz short near ptr loc_4011C4+1
75 01 jnz short near ptr loc_4011C4+1
loc_4011C4: ...
E8 58 C3 90 90 call near ptr 90D0D521h
由于总是优先处理false分支,而没有来得及查看跳转地址究竟在哪儿,从而意识不到跳转地址是同一个,并且就在附近。
E8以及之后的字节被翻译成Call指令是完全错误的,因为根本不会被执行。实际上E8是植入的流氓字节,它掩盖了58 C3作为pop eax,ret这两条真实的指令。
手工修正,将E8修正为数据,将loc_4011C5处转换成指令:
74 03 jz short near ptr loc_4011C5
75 01 jnz short near ptr loc_4011C5
E8 db 0E8h
loc_4011C5: ...
58 pop eax
C3 ret
2、永真条件的跳转指令
另一种常见的技术是由跳转条件恒定的跳转指令构成的。例如:
与上面一种方法类似,通过手动将eax清零(xor eax,eax)使得jz总是无条件执行。反汇编器总是先处理false分支,因而E9这个流氓字节又被错误地翻译成了jmp指令,掩盖了其后的真实指令pop,ret
3、无效的反汇编指令
前面所讲的技巧是插入流氓字节,用其掩盖随后的真实指令,这些流氓字节可以被忽略。
然而也有一种情况,流氓字节不能被忽略,并且甚至还参与到代码执行中。例如:
这串代码写得真是巧妙,加黑的字节被当作两个指令的一部分,执行了两次。顺着代码跳转逻辑,我们发现,实际上最终会落到E8之后的Real Code中,E8本身不会被执行。然而反汇编器还是把E8翻译成call指令。
这种情况下,需要用IDA Python脚本将这些重叠执行、无法取舍的指令变成nop指令,留下实际影响指令(即 xor eax,eax)
三、混淆控制流图
混淆控制流是一种对抗反汇编的方法,可以使分析人员忽视一些代码,通过模糊函数与其他函数掉调用关系达到隐藏函数的目的。这种情况下,交叉引用可能失灵,为分析带来困难。常见的方法有:
1、刻意使用函数指针
刻意使用函数指针,大大降低了反汇编器自动推导出的信息,从而隐藏了交叉函数引用。例如: