脱壳的艺术
Mark Vincent Yason
概述:脱壳是门艺术——脱壳既是一种心理挑战,同时也是逆向领域最为激动人心的智力游戏之一。为了甄别或解决非常难的反逆向技巧,逆向分析人员有时不得不了解操作系统的一些底层知识,聪明和耐心也是成功脱壳的关键。这个挑战既牵涉到壳的创建者,也牵涉到那些决心躲过这些保护的脱壳者。
本文主要目的是介绍壳常用的反逆向技术,同时也探讨了可以用来躲过或禁用这些保护的技术及公开可用的工具。这些信息将使研究人员特别是恶意代码分析人员在分析加壳的恶意代码时能识别出这些技术,当这些反逆向技术阻碍其成功分析时能决定下一步的动作。第二个目的,这里介绍的信息也会被那些计划在软件中添加一些保护措施用来减缓逆向分析人员分析其受保护代码的速度的研究人员用到。当然没有什么能使一个熟练的、消息灵通的、坚定的逆向分析人员止步的。
关键词:逆向工程、壳、保护、反调试、反逆向
3 断点和补丁检测技术
本节列举了壳最常用的识别软件断点、硬件断点和补丁的方法。
3.1 Software Breakpoint Detection
软件断点是通过修改目标地址代码为0xCC(INT3/Breakpoint Interrupt)来设置的断点。壳通过在受保护的代码段和(或)API函数中扫描字节0xCC来识别软件断点。
示例
检测可能和下面一样简单:
cld
mov edi,Protected_Code_Start
mov ecx,Protected_Code_End - Protected_Code_Start
mov al,0xcc
repne scasb
jz .breakpoint_found
有些壳对比较的字节值作了些运算使得检测变得不明显,例如:
if ( byte XOR 0x55 == 0x99 ) then breakpoint found
Where: 0x99 == 0xCC XOR 0x55
对策
如果软件断点被发现了逆向分析人员可以使用硬件断点来代替。如果需要在API内部下断,但是壳又检测API内部的断点,逆向分析人员可以在最终被ANSI版API调用的UNICODE版的API下断(如:用LoadLibraryExW代替LoadLibraryA),或者用相应的native API来代替。
3.2 Hardware Breakpoint Detection
另一种断点称之为硬件断点,硬件断点是通过设置名为Dr0到Dr7的调试寄存器12来实现的。Dr0-Dr3包含至多4个断点的地址,Dr6是个标志,它指示哪个断点被触发了,Dr7包含了控制4个硬件断点诸如启用/禁用或者中断于读/写的标志。
由于调试寄存器无法在Ring3下访问,硬件断点的检测需要执行一小段代码。壳利用了含有调试寄存器值的CONTEXT结构,CONTEXT结构可以通过传递给异常处理例程的ContextRecord参数来访问。
示例
这是一段查询调试寄存器的示例代码:
; set up exception handler
push .exception_handler
push dword [fs:0]
mov [fs:0],esp
;eax will be 0xFFFFFFFF if hardware breakpoints are identified
xor eax,eax
;throw an exception
mov dword [eax],0
;restore exception handler
pop dword [fs:0]
add esp,4
;test if EAX was updated (breakpoint identified)
test eax,eax
jnz .breakpoint_found
:::
.exception_handler
;EAX = CONTEXT record
mov eax,[esp+0xc]
;check if Debug Registers Context.Dr0-Dr3 is not zero
cmp dword [eax+0x04],0
jne .hardware_bp_found
cmp dword [eax+0x08],0
jne .hardware_bp_found
cmp dword [eax+0x 0c ],0
jne .hardware_bp_found
cmp dword [eax+0x10],0
jne .hardware_bp_found
jmp .exception_ret
.hardware_bp_found
;set Context.EAX to signal breakpoint found
mov dword [eax+0xb0],0xFFFFFFFF
.exception_ret
;set Context.EIP upon return
add dword [eax+0xb8],6
xor eax,eax
retn
有些壳也利用调试寄存器的值作为解密密钥的一部分。这些调试寄存器要么初始化为一个特定值要么为0。因此,如果这些调试寄存器被修改,解密将会失败。当解密的代码是受保护的程序或者脱壳代码的一部分的时候,将导致无效指令并造成程序一些意想不到的终止。
对策
如果壳没检测软件断点,逆向分析人员可以尝试使用软件断点,同样OllyDbg的内存读/写断点也可以使用。当逆向分析人员需要设置API断点的时候在native或者是UNICODE版的API内部设软件断点也是可行的。
3.3 Patching Detection via Code Checksum Calculation
补丁检测技术能识别壳的代码是否被修改(代码被修改则意味着反调试例程已经被禁用了),其次也能识别是否设置了软件断点。补丁检测是通过代码校验来实现的,校验计算包括从简单到复杂的校验和/哈希算法。
示例
下面是一个比较简单的校验和计算的例子:
mov esi,Protected_Code_Start
mov ecx,Protected_Code_End - Protected_Code_Start
xor eax,eax
.checksum_loop
movzx ebx,byte [esi]
add eax,ebx
rol eax,1
inc esi
loop .checksum_loop
cmp eax,dword [.dwCorrectChecksum]
jne .patch_found
对策
如果代码校验例程识别出了软件断点,可以用硬件断点来代替。如果校验例程识别出了代码补丁,逆向分析人员可以通过在补丁地址设置内存访问断点来定位校验例程所在,一旦发现了校验例程,可以修改校验和为预期的值或者在比较失败后修改适当的标志。