- UID
- 33818
注册时间2007-8-9
阅读权限10
最后登录1970-1-1
周游历练
该用户从未签到
|
楼主 |
发表于 2007-9-1 15:24:00
|
显示全部楼层
2.11 Device Drivers
检测内核模式的调试器是否活跃于系统中的典型技术是访问他们的设备驱动程序。该技术相当简单,仅涉及调用kernel32!CreateFile()检测内核模式调试器(如SoftICE)使用的那些众所周知的设备名称。
示例
一个简单的检查如下:
push NULL
push 0
push OPEN_EXISTING
push NULL
push FILE_SHARE_READ
push GENERIC_READ
push .szDeviceNameNtice
call [CreateFileA]
cmp eax,INVALID_HANDLE_VALUE
jne .debugger_found
.szDeviceNameNtice db "\\.\NTICE",0
某些版本的SoftICE会在设备名称后附加数字导致这种检查失败,逆向论坛中相关的描述是穷举附加的数字直到发现正确的设备名称。新版壳也用设备驱动检测技术检测诸如Regmon和Filemon之类的系统监视程序的存在。
对策
一种简单的方法就是在kernel32!CreateFileW()内设置断点,断下来后,要么操纵FileName参数要么改变其返回值为INVALID_HANDLE_VALUE(0xFFFFFFFF)。
2.12 OllyDbg:Guard Pages
这个检查是针对OllyDbg的,因为它和OllyDbg的内存访问/写入断点特性相关。
除了硬件断点和软件断点外,OllyDbg允许设置一个内存访问/写入断点,这种类型的断点是通过页面保护11来实现的。简单地说,页面保护提供了当应用程序的某块内存被访问时获得通知这样一个途径。
页面保护是通过PAGE_GUARD页面保护修改符来设置的,如果访问的内存地址是受保护页面的一部分,将会产生一个STATUS_GUARD_PAGE_VIOLATION(0x80000001)异常。如果进程被OllyDbg调试并且受保护的页面被访问,将不会抛出异常,访问将会被当作内存断点来处理,而壳正好利用了这一点。
示例
下面的示例代码中,将会分配一段内存,并将待执行的代码保存在分配的内存中,然后启用页面的PAGE_GUARD属性。接着初始化标设符EAX为0,然后通过执行内存中的代码来引发STATUS_GUARD_PAGE_VIOLATION异常。如果代码在OllyDbg中被调试,因为异常处理例程不会被调用所以标设符将不会改变。
;set up exception handler
push .exception_handle
push dword [fs:0]
mov [fs:0],esp
;allocate memory
push PAGE_READWRITE
push MEM_COMMIT
push 0x1000
push NULL
call [VirtualAlloc]
test eax,eax
jz .failed
mov [.pAllocatedMem],eax
;store a RETN on the allocated memory
mov byte [eax],0xC3
;then set the PAGE_GUARD attribute of the allocated memory
lea eax,[.dwOldProtect]
push eax
push PAGE_EXECUTE_READ | PAGE_GUARD
push 0x1000
push dword [.pAllocatedMem]
call [VirtualProtect]
;set marker (EAX) as 0
xor eax,eax
;trigger a STATUS_GUARD_PAGE_VIOLATION exception
call [.pAllocatedMem]
;check if marker had not been changed (exception handler not called)
test eax,eax
je .debugger_found
.exception_handler
;EAX = CONTEXT record
mov eax,[esp+0xC]
;set marker (CONTEXT.EAX) to 0xFFFFFFFF
;to signal that the exception handler was called
mov dword [eax+0xb0],0xFFFFFFFF
xor eax,eax
retn
对策
由于页面保护引发一个异常,逆向分析人员可以故意引发一个异常,这样异常处理例程将会被调用。在示例中,逆向分析人员可以用INT3指令替换掉RETN指令,一旦INT3指令被执行,Shift+F9强制调试器执行异常处理代码。这样当异常处理例程调用后,EAX将被设为正确的值,然后RETN指令将会被执行。
如果异常处理例程里检查异常是否真地是STATUS_GUARD_PAGE_VIOLATION,逆向分析人员可以在异常处理例程中下断点然后修改传入的ExceptionRecord参数,具体来说就是ExceptionCode, 手工将ExceptionCode设为STATUS_GUARD_PAGE_VIOLATION即可。
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+0x0c],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
对策
如果代码校验例程识别出了软件断点,可以用硬件断点来代替。如果校验例程识别出了代码补丁,逆向分析人员可以通过在补丁地址设置内存访问断点来定位校验例程所在,一旦发现了校验例程,可以修改校验和为预期的值或者在比较失败后修改适当的标志。
4反分析技术
反分析技术的目标是减缓逆向分析人员对受保护代码和(或)加壳后的程序分析和理解的速度。我们将讨论诸如加密/压缩、垃圾代码、代码变形、反-反编译等技术,这些技术的目的是为了混淆代码、考验耐心、浪费逆向分析人员的时间,解决这些问题需要逆向分析人员拥有耐心、聪慧等品质。
4.1 Encryption and Compression
加密和压缩是最基本的反分析形式。它们初步设防,防止逆向分析人员直接在反编译器内加载受保护的程序然后没有任何困难地开始分析。
加密 壳通常都既加密本身代码也加密受保护的程序。不同的壳所采用的加密算法大不相同,有非常简单的XOR循环,也有执行数次运算的非常复杂的循环。对于某些多态变形壳,为了防止查壳工具正确地识别壳,每次加壳所采用的加密算法都不同,解密代码也通过变形显得很不一样。
解密例程作为一个取数、计算、存诸操作的循环很容易辨认。下面是一个对加密过的DWORD值执行数次XOR操作的简单的解密例程。
0040A07C LODS DWORD PTR DS:[ESI]
0040A07D XOR EAX,EBX
0040A07F SUB EAX,12338CC3
0040A084 ROL EAX,10
0040A087 XOR EAX,799F82D0
0040A08C STOS DWORD PTR ES:[EDI]
0040A08D INC EBX
0040A08E LOOPD SHORT 0040A07C ;decryption loop
这里是另一个多态变形壳的解密例程:
00476056 MOV BH,BYTE PTR DS:[EAX]
00476058 INC ESI
00476059 ADD BH,0BD
0047605C XOR BH,CL
0047605E INC ESI
0047605F DEC EDX
00476060 MOV BYTE PTR DS:[EAX],BH
00476062 CLC
00476063 SHL EDI,CL
:::More garbage code
00476079 INC EDX
0047607A DEC EDX
0047607B DEC EAX
0047607C JMP SHORT 0047607E
0047607E DEC ECX
0047607F JNZ 00476056 ;decryption loop
下面是由同一个多态壳生成的另一段解密例程:
0040C045 MOV CH,BYTE PTR DS:[EDI]
0040C047 ADD EDX,EBX
0040C049 XOR CH,AL
0040C04B XOR CH,0D9
0040C04E CLC
0040C04F MOV BYTE PTR DS:[EDI],CH
0040C051 XCHG AH,AH
0040C053 BTR EDX,EDX
0040C056 MOVSX EBX,CL
::: More garbage code
0040C067 SAR EDX,CL
0040C06C NOP
0040C06D DEC EDI
0040C06E DEC EAX
0040C06F JMP SHORT 0040C071
0040C071 JNZ 0040C045 ;decryption loop
上面两个示例中高亮的行是主要的解密指令,其余的指令都是用来迷惑逆向分析人员的垃圾代码。注意寄存器是如何交换的,还有两个示例之间解密方法是如何改变的。
Compression 压缩的主要目的是为了缩小可执行文件代码和数据的大小,但是由于原始的包含可读字符串的可执行文件变成了压缩数据,因此也有那么一些混淆的作用。看看几款壳所使用的压缩引擎:UPX使用NRV(Not Really Vanished)和LZMA(Lempel-Ziv-Markov chain-Algorithm),FSG使用aPLib,Upack使用LZMA,yoda加密壳使用LZO。这其中有些压缩引擎可以自由地使用于非商业应用,但是商业应用需要许可/注册。
对策
解密和解压缩循环很容易就能被躲过,逆向分析人员只需要知道解密和解压缩循环何时结束,然后在循环结束后面的指令上下断点。记住,有些壳会在解密循环中检测断点。
4.2 Garbage Code and Code Permutation
Garbage Code 在脱壳的例程中插入垃圾代码是另一种有效地迷惑逆向分析人员的方法。它的目的是在加密例程或者诸如调试器检测这样的反逆向例程中掩盖真正目的的代码。通过将本文描述过的调试器/断点/补丁检测技术隐藏在一大堆无关的、不起作用的、混乱的指令中,垃圾代码可以增加这些检测的效果。此外,有效的垃圾代码是那些看似合法/有用的代码。
示例
下面是一段在相关的指令中插入了垃圾代码的解密例程:
0044A21A JMP SHORT sample.0044A21F
0044A21C XOR DWORD PTR SS:[EBP],6E4858D
0044A223 INT 23
0044A225 MOV ESI,DWORD PTR SS:[ESP]
0044A228 MOV EBX,2C322FF0
0044A22D LEA EAX,DWORD PTR SS:[EBP+6EE5B321]
0044A233 LEA ECX DWORD PTR DS:[ESI+543D583E]
0044A239 ADD EBP,742C0F15
0044A23F ADD DWORD PTR DS:[ESI],3CB3AA25
0044A245 XOR EDI,7DAC77E3
0044A24B CMP EAX,ECX
0044A24D MOV EAX,5ACAC514
0044A252 JMP SHORT sample.0044A257
0044A254 XOR DWORD PTR SS:[EBP],AAE47425
0044A25B PUSH ES
0044A25C ADD EBP,5BAC5C22
0044A262 ADC ECX,3D71198C
0044A268 SUB ESI,-4
0044A26B ADC ECX,3795A210
0044A271 DEC EDI
0044A272 MOV EAX,2F57113F
0044A277 PUSH ECX
0044A278 POP ECX
0044A279 LEA EAX,DWORD PTR SS:[EBP+3402713D]
0044A27F EDC EDI
0044A280 XOR DWORD PTR DS:[ESI],33B568E3
0044A286 LEA EBX,DWORD PTR DS:[EDI+57DEFEE2]
0044A28C DEC EDI
0044A28D SUB EBX,7ECDAE21
0044A293 MOV EDI,185C5C6C
0044A298 MOV EAX,4713E635
0044A29D MOV EAX,4
0044A2A2 ADD ESI,EAX
0044A2A4 MOV ECX,1010272F
0044A2A9 MOV ECX,7A49B614
0044A2AE CMP EAX,ECX
0044A2B0 NOT DWORD PTR DS:[ESI]
示例中相关的解密指令是:
0044A225 MOV ESI,DWORD PTR SS:[ESP]
0044A23F ADD DWORD PTR DS:[ESI],3CB3AA25
0044A268 SUB ESI,-4
0044A280 XOR DWORD PTR DS:[ESI],33B568E3
0044A29D MOV EAX,4
0044A2A2 ADD ESI,EAX
0044A2B0 NOT DWORD PTR DS:[ESI]
Code Permutation 代码变形是更高级壳使用的另一种技术。通过代码变形,简单的指令变成了复杂的指令序列。这要求壳理解原有的指令并能生成新的执行相同操作的指令序列。
一个简单的指令置换示例:
mov eax,ebx
test eax,eax
转换成下列等价的指令:
push ebx
pop eax
or eax,eax
结合垃圾代码使用,代码变形是一种有效地减缓逆向分析人员理解受保护代码速度的技术。
示例
为了说明,下面是一个通过代码变形并在置换后的代码间插入了垃圾代码的调试器检测例程:
004018A8 MOV ECX,A104B412
004018AD PUSH 004018C1
004018B2 RETN
004018B3 SHR EDX,5
004018B6 ADD ESI,EDX
004018B8 JMP SHORT 004018BA
004018BA XOR EDX,EDX
004018BC MOV EAX,DWORD PTR DS:[ESI]
004018BE STC
004018BF JB SHORT 004018DE
004018C1 SUB ECX,EBX
004018C3 MOV EDX,9A01AB1F
004018C8 MOV ESI,DWORD PTR FS:[ECX]
004018CB LEA ECX DWORD PTR DS:[EDX+FFFF7FF7]
004018D1 MOV EDX,600
004018D6 TEST ECX,2B73
004018DC JMP SHORT 004018B3
004018DE MOV ESI,EAX
004018E0 MOV EAX,A35ABDE4
004018E5 MOV ECX,FAD1203A
004018EA MOV EBX,51AD5EF2
004018EF DIV EBX
004018F1 ADD BX,44A5
004018F6 ADD ESI,EAX
004018F8 MOVZX EDI,BYTE PTR DS:[ESI]
004018FB OR EDI,EDI
004018FD JNZ SHORT 00401906
其实这是一个很简单的调试器检测例程:
00401081 MOV EAX,DWORD PTR FS:[18]
00401087 MOV EAX,DWORD PTR DS:[EAX+30]
0040108A MOVZX EAX,BYTE PTR DS:[EAX+2]
0040108E TEST EAX,EAX
00401090 JNZ SHORT 00401099
对策
垃圾代码和代码变形是一种用来考验耐心和浪费逆向分析人员的时间的方式。因此,重要的是知道这些混淆技术背后隐藏的指令是否值得去理解(是不是仅仅执行解密、壳的初始化等动作)。
避免跟踪进入这些难懂的指令的方法之一是在壳最常用的API下断点(如:VirtualAlloc,VitualProtect,LoadLibrary,GetProcAddress等)并把这些API当作跟踪的标志。如果在这些跟踪标志之间出了错,这时候就对这一段代码进行详细的跟踪。另外,设置内存访问/写入断点也让逆向分析人员能有针对性地分析那些修改/访问受保护进程最有趣的部分的代码,而不是跟踪大量的代码最终却(很可能)发现是一个确定的例程。
最后,在VMWare中运行OllyDbg并不时地保存调试会话快照,这样一来逆向分析人员就可以回到某一个特定的跟踪状态。如果出了错,可以返回到某一特定的跟踪状态继续跟踪分析。 |
|