反汇编程序的工作原理
最简单的反汇编程序超级简单,但它们也可能非常复杂。更高级的反汇编器会尝试识别函数(可能具有倍数返回)、跳转表等习语,并且不会被反汇编技巧所欺骗。它们分为两大类。
-
线性 - 按顺序分解所有指令,从某个点(通常是二进制文件的入口点)开始。
-
面向流 - 它们跟随跳跃和呼叫,并继续从目标中分解。它们也可能在返回指令后停止反汇编,因此请避免显示无法访问的指令(因此可能根本不是代码)。
由于面向流的反汇编程序遵循分支,并且存在条件分支,因此反汇编程序必须做出决定。通常,对于普通代码,反汇编器可以简单地遵循两者(例如,跳跃和不跳跃,从目标和下一条指令中反汇编)。问题在于可能存在矛盾或不兼容的跳跃。
欺骗面向流的反汇编程序
有多种方法可以欺骗反汇编程序。这里只是其中的几个:
-
放置两个连续但“相反”的条件分支,例如 a 后跟 a 。
jz
jnz
-
使用常量条件,例如 .
xor eax, eax; jz <addr>
-
使用一个什么都不做的分支,例如 ,然后在 addr: 处。这通常在 shellcode 中用于获取带内数据的地址,因为在 x86 上,这是获取 PC 周围地址的最简单方法。
call <addr>
pop <reg>
-
使用一系列字节,这些字节将多次执行,作为不同的指令,具体取决于 PC 所在的位置。例如。当反汇编程序将其反汇编(如 x86)时,它将看到 ,然后是 ,这不是有效的操作码,最后是 。
EB FF C0 48
EB FF
jmp 1
C0
48
dec eax
问题是这不是它的执行方式!从指令的开头跳转一个字节(或者更确切地说,从指令的末尾跳转 -1 字节 (0xFF)。这使得 EIP 降落在0xFF上。现在 CPU 解码为 和 和 。最后。这段代码基本上什么都不做。这里的解决方案是:NOP出所有四个字节。
jmp 1
FF C0
inc eax
48
dec eax
-
滥用和扰乱功能边界。例如,这是 .这会将返回地址推送到堆栈上,该地址将是 .然后将此地址弹出到 PC 中,这有效地使这两个入侵组合变得无用。但是,这个 cal 使反汇编程序认为函数到此结束,并且下一条指令是另一个函数的结束。
call
ret
E8 00 00 00 00 C3
call 5; ret
ret
ret
-
大量使用函数指针。虽然这样做的目的不是为了让逆向工程师的生活更加困难,但它也有同样的效果。从本质上讲,每当复制指针时,地址都会有交叉引用,但是当它被调用时,由于它是从寄存器或内存地址调用的,因此反汇编器通常无法确定何时使用它。
缓解
绕过这些技巧最困难的部分与修补它们无关——这是微不足道的。诀窍在于快速识别它们,而不是浪费时间弄清楚它们的作用。这只能通过实践来完成。我所学到的最重要的反反汇编知识,都是从第16章的《实用恶意软件分析》一书中学到的。这本书包括实验室,我建议你这样做。你可以在这里看到我的文章。
一旦它们被识别出来,IDA Pro 就很容易修复。在大多数情况下,我最喜欢的修复方法是使用 .这可以通过“File > IDC Command...”完成对话框,如果不想提供地址,则可以使用光标的地址。这通常看起来像 .确保为要删除的每个字节运行它。PatchByte
ScreenEA
PatchByte(ScreenEA(), 0x90);
当您知道从函数指针调用哪个函数时(显然,当从此位置调用多个函数时要小心),您可以使用 手动添加外部参照。你要用的就是这样,确保你已经选择了调用指令。您可以对跳跃做同样的事情,用fl_CN代替fl_JN。AddCodeXref
AddCodeXref(ScreenEA(), <addr of function>, fl_CN);
原文始发于微信公众号(天启者安全):防拆技术和缓解措施
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论