| 
注册时间2007-8-9
阅读权限10
最后登录1970-1-1UID33818 周游历练 
 
 该用户从未签到 | 
 
 
 楼主|
发表于 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并不时地保存调试会话快照,这样一来逆向分析人员就可以回到某一个特定的跟踪状态。如果出了错,可以返回到某一特定的跟踪状态继续跟踪分析。
 | 
 |