关键词:逆向工程、壳、保护、反调试、反逆向
1简
介
在逆向工程领域,壳是最有趣的谜题之一。在解谜的过程中,逆向分析人员会获得许多关于系统底层、逆向技巧等知识。
壳(这个术语在本文中既指压缩壳也包括加密壳)是用来防止程序被分析的。它们被商业软件合法地用于防止信息披露、篡改及盗版。可惜恶意软件也基于同样的理由在使用壳,只不过动机不良。
由于大量恶意软件存在加壳现象,研究人员和恶意代码分析人员为了分析代码,开始学习脱壳的技巧。但是随着时间的推移,为防止逆向分析人员分析受保护的程序并成功脱壳,新的反逆向技术也被不断地添加到壳中。并且战斗还在继续,新的反逆向技术被开发的同时逆向分析人员也在针锋相对地发掘技巧、研究技术并开发工具来对付它们。
本文主要关注于介绍壳所使用的反逆向技术,同时也探讨了躲过/禁用这些保护措施的工具及技术。可能有些壳通过抓取进程映像(dump)能够轻易被搞定,这时处理反逆向技术似乎没有必要,但是有些情况下加密壳的代码需要加以跟踪和分析,例如:
需要躲过部分加密壳代码以便抓取进程映像、让输入表重建工具正确地工作。 深入分析加密壳代码以便在一个反病毒产品中整合进脱壳支持。
此外,当反逆向技术被恶意程序直接应用,以防止跟踪并分析其恶意行为时,熟悉反逆向技术也是很有价值的。
本文绝不是一个完整的反逆向技术的清单,因为它只涵盖了壳中常用的、有趣的一些技术。建议读者参阅最后一节的链接和图书资料,以了解更多其他逆向及反逆向的技术。
笔者希望您觉得这些材料有用,并能应用其中的技术。脱壳快乐!
2 调试器检测技
术 本节列出了壳用来确定进程是否被调试或者系统内是否有调试器正在运行的技术。这些调试器检测技术既有非常简单(明显)的检查,也有涉及到native APIs和内核对象的。
2.1 PEB.BeingDebugged Flag : IsDebuggerPresent()
最基本的调试器检测技术就是检测进程环境块(PEB)1中的BeingDebugged标志。kernel32!IsDebuggerPresent() API检查这个标志以确定进程是否正在被用户模式的调试器调试。
下面显示了IsDebuggerPresent() API的实现代码。首先访问线程环境块(TEB)2得到PEB的地址,然后检查PEB偏移0x02位置的BeingDebugged标志。 mov eax, large fs: 18h mov eax, [eax+30h]
movzx eax, byte ptr [eax+2] retn
除了直接调用IsDebuggerPresent(),有些壳会手工检查PEB中的BeingDebugged标志以防逆向分析人员在这个API上设置断点或打补丁。 示例
下面是调用IsDebuggerPresent() API和使用PEB.BeingDebugged标志确定调试器是否存在的示例代码。
;call kernel32!IsDebuggerPresent() call [IsDebuggerPresent] test eax,eax
jnz .debugger_found
;check PEB.BeingDebugged directly
Mov eax,dword [fs:0x30] ;EAX = TEB.ProcessEnvironmentBlock
movzx eax,byte [eax+0x02] ;AL = PEB.BeingDebugged test eax,eax
jnz .debugger_found 由于这些检查很明显,壳一般都会用后面章节将会讨论的垃圾代码或者反—反编译技术进行混淆。 对策
人工将PEB.BeingDebugged标志置0可轻易躲过这个检测。在数据窗口中Ctrl+G(前往表达式)输入fs:[30],可以在OllyDbg中查看PEB数据。 另外Ollyscript命令\可以补丁这个标志。 dbh
最后,Olly Advanced3 插件有置BeingDebugged标志为0的选项。
2.2 PEB.NtGlobalFlag , Heap.HeapFlags, Heap.ForceFlags
PEB.NtGlobalFlag PEB另一个成员被称作NtGlobalFlag(偏移0x68),壳也通过它来检测程序是否用调试器加载。通常程序没有被调试时,NtGlobalFlag成员值为0,如果进程被调试这个成员通常值为0x70(代表下述标志被设置): FLG_HEAP_ENABLE_TAIL_CHECK(0X10) FLG_HEAP_ENABLE_FREE_CHECK(0X20) FLG_HEAP_VALIDATE_PARAMETERS(0X40)
这些标志是在ntdll!LdrpInitializeExecutionOptions()里设置的。请注意PEB.NtGlobalFlag的默认值可以通过gflags.exe工具或者在注册表以下位置创建条目来修改:
HKLM\\Software\\Microsoft\\Windows Nt\\CurrentVersion\\Image File Execution Options Heap Flags 由于NtGlobalFlag标志的设置,堆也会打开几个标志,这个变化可以在ntdll!RtlCreateHeap()里观测到。通常情况下为进程创建的第一个堆会将其Flags和ForceFlags4分别设为0x02(HEAP_GROWABLE)和0 。然而当进程被调试时,这两个标志通常被设为0x50000062(取决于NtGlobalFlag)和
0x40000060(等于Flags AND 0x6001007D)。默认情况下当一个被调试的进程创建堆时下列附加的堆标志将被设置: HEAP_TAIL_CHECKING_ENABLED(0X20)
HEAP_FREE_CHECKING_ENABLED(0X40) 示例
下面的示例代码检查PEB.NtGlobalFlag是否等于0,为进程创建的第一个堆是否设置了附加标志(PEB.ProcessHeap): ;ebx = PEB
Mov ebx,[fs:0x30]
;Check if PEB.NtGlobalFlag != 0 Cmp dword [ebx+0x68],0 jne .debugger_found
;eax = PEB.ProcessHeap
Mov eax,[ebx+0x18]
;Check PEB.ProcessHeap.Flags
Cmp dword [eax+0x0c],2 jne .debugger_found
;Check PEB.ProcessHeap.ForceFlags Cmp dword [eax+0x10],0 jne .debugger_found 对策
可以将 PEB.NtGlobalFlag和PEB.HeapProcess标志补丁为进程未被调试时的相应值。下面是一个补丁上述标志的ollyscript示例: Var peb
var patch_addr var process_heap
//retrieve PEB via a hardcoded TEB address( first thread: 0x7ffde000) Mov peb,[7ffde000+30]
//patch PEB.NtGlobalFlag
Lea patch_addr,[peb+68] mov [patch_addr],0
//patch PEB.ProcessHeap.Flags/ForceFlags Mov process_heap,[peb+18]
lea patch_addr,[process_heap+0c] mov [patch_addr],2
lea patch_addr,[process_heap+10] mov [patch_addr],0
同样地Olly Advanced插件有设置PEB.NtGlobalFlag和PEB.ProcessHeap的选项。
2.3 DebugPort:
CheckRemoteDebuggerPresent()/NtQueryInformationProcess()
Kernel32!CheckRemoteDebuggerPresent()是另一个可以用于确定是否有调试器被附加到进程的API。这个API内部调用了
ntdll!NtQueryInformationProcess(),调用时ProcessInformationclass参数为ProcessDebugPort(7)。而NtQueryInformationProcess()检索内核结构
EPROCESS5的DebugPort成员。非0的DebugPort成员意味着进程正在被用户模式的调试器调试。如果是这样的话,ProcessInformation 将被置为0xFFFFFFFF ,否则ProcessInformation 将被置为0。
Kernel32!CheckRemoteDebuggerPresent()接受2个参数,第1个参数是进程句柄,第2个参数是一个指向boolean变量的指针,如果进程被调试,该变量将包含TRUE返回值。
BOOL CheckRemoteDebuggerPresent( HANDLE hProcess,
PBOOL pbDebuggerPresent )
ntdll!NtQueryInformationProcess()有5个参数。为了检测调试器的存在,需要将ProcessInformationclass参数设为ProcessDebugPort(7): NTSTATUS NTAPI NtQueryInformationProcess( HANDLE ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation,
ULONG ProcessInformationLength, PULONG ReturnLength ) 示例
下面的例子显示了如何调用CheckRemoteDebuggerPresent()和NtQueryInformationProcess()来检测当前进程是否被调试: ; using Kernel32!CheckRemoteDebuggerPresent() lea eax,[.bDebuggerPresent]
push eax ;pbDebuggerPresent push 0xffffffff ;hProcess call [CheckRemoteDebuggerPresent] cmp dword [.bDebuggerPresent],0 jne .debugger_found
; using ntdll!NtQueryInformationProcess(ProcessDebugPort) lea eax,[.dwReturnLen]
push eax ;ReturnLength
push 4 ;ProcessInformationLength lea eax,[.dwDebugPort]
push eax ;ProcessInformation
push ProcessDebugPort ;ProcessInformationClass(7) push 0xffffffff ;ProcessHandle
call [NtQueryInformationProcess] cmp dword [.dwDebugPort],0 jne .debugger_found 对策
一种方法是在NtQueryInformationProcess()返回的地方设置断点,当这个断点被断下来后,将ProcessInformation 补丁为0。 下面是自动执行这个方法的ollyscript示例:
var bp_NtQueryInformationProcess
// set a breakpoint handler
eob bp_handler_NtQueryInformationProcess
// set a breakpoint where NtQueryInformationProcess returns gpa \find $RESULT,#C21400# //retn 14
mov bp_NtQueryInformationProcess,$RESULT bphws bp_NtQueryInformationProcess,\run
bp_handler_NtQueryInformationProcess:
//ProcessInformationClass == ProcessDebugPort? cmp [esp+8],7
jne bp_handler_NtQueryInformationProcess_continue
//patch ProcessInformation to 0 mov patch_addr,[esp+c] mov [patch_addr],0
// clear breakpoint
bphwc bp_NtQueryInformationProcess
bp_handler_NtQueryInformationProcess_continue: run
Olly Advanced插件有一个patch NtQueryInformationProcess()的选项,这个补丁涉及注入一段代码来操纵NtQueryInformationProcess()的返回值。 2.4 Debugger Interrupts
在调试器中步过INT3和INT1指令的时候,由于调试器通常会处理这些调试中断,所以异常处理例程默认情况下将不会被调用,Debugger Interrupts就利用了这个事实。这样壳可以在异常处理例程中设置标志,通过INT指令后如果这些标志没有被设置则意味着进程正在被调试。另外,kernel32!DebugBreak()内部是调用了INT3来实现的,有些壳也会使用这个API。 示例