脱壳的艺术--6. 高级及其它技术

 

脱壳的艺术

Mark Vincent Yason

概述:脱壳是门艺术——脱壳既是一种心理挑战,同时也是逆向领域最为激动人心的智力游戏之一。为了甄别或解决非常难的反逆向技巧,逆向分析人员有时不得不了解操作系统的一些底层知识,聪明和耐心也是成功脱壳的关键。这个挑战既牵涉到壳的创建者,也牵涉到那些决心躲过这些保护的脱壳者。

本文主要目的是介绍壳常用的反逆向技术,同时也探讨了可以用来躲过或禁用这些保护的技术及公开可用的工具。这些信息将使研究人员特别是恶意代码分析人员在分析加壳的恶意代码时能识别出这些技术,当这些反逆向技术阻碍其成功分析时能决定下一步的动作。第二个目的,这里介绍的信息也会被那些计划在软件中添加一些保护措施用来减缓逆向分析人员分析其受保护代码的速度的研究人员用到。当然没有什么能使一个熟练的、消息灵通的、坚定的逆向分析人员止步的。

6. 高级及其它技术                                                       

本节罗列了不属于前面任一分类的一些高级和其它的反逆向技术。

6.1 Process Injection

进程注入已经成为某些壳的一个特点。脱壳代码打开一个选定的宿主进程(自身、explorer.exeiexplorer.exe等)然后将脱壳后的程序注入到这个宿主进程。

 

下面是一个支持进程注入的壳的屏幕截图。

脱壳的艺术--6. 高级及其它技术_第1张图片

恶意代码利用壳的这个特点使它们能躲过一些防火墙,这些防火墙通过检查进程是否在获准进行外部网络连接的应用程序列表中而决定是否放行。

壳所采用的执行进程注入的一种方法如下:

1. kernel32!CreateProcess()传递CREATE_SUSPENDED进程创建标志,将宿主进程作为一个挂起的子进程打开。这时一个初始化了的线程被创建并挂起,由于loader例程(ntdll!LrdInitializeThunk)还没有被调用,DLL还没有被载入。这个线程的上下文中包含PEB地址、宿主进程入口点信息的寄存器值被设置。

2. 使用kernel32!GetThreadContext()获取子进程初始化线程的上下文。

3. 通过CONTEXT.EBX获取子进程的PEB地址。

4. PEB.ImageBase(PEB+0x8)获取子进程的映像基址。

5. BaseAddress参数指向检索到的映像基址,调用ntdll!NtUnmapViewOfSection()unmap子进程中的原始宿主映像。

6. 脱壳代码使用kernel32!VirtualAllocEx()在子进程中分配一段内存,dwSize参数等于脱壳后程序的映像大小。

7. 使用kernel32!WriteProcessMemory()将脱壳后的程序的PE头和每个节写入子进程。

8. 将子进程的PEB.ImageBase更新以匹配脱壳后的程序映像基址。

9. 通过kernel32!SetThreadContext()更新子进程初始化线程的上下文,将其中的CONTEXT.EAX设置为脱壳后程序的入口点。

10. 通过kernel32!ResumeThread()恢复子进程的执行。

为了从入口点开始调试打开的子进程,逆向分析人员可以在WriteProcessMemory()中设置断点,当包含入口点的节被写入子进程的时候,将入口点代码补丁为跳往自身指令(0xEB0xFE)。当子进程的主线程被恢复,子进程将在入口点进入一个死循环。这时逆向分析人员就可以附加一个调试器到子进程,恢复被修改的指令,继续正常的调试。

6.2 Debugger Blocker

Armadillo壳引入了称之为Debugger Blocker的功能,它可以阻止逆向分析人员将调试器附加到一个受保护的进程。这个保护是通过调用Windows提供的调试函数来实现的。

具体来说就是脱壳代码扮演一个调试器的角色(父进程),通过它打开、调试/控制包含脱壳后程序的子进程。

由于受保护的进程已经被调试,通过kernel32!DebugActiveProcess()来附加调试器将会失败,原因是相应的native API ntdll!NtDebugActiveProcess()将返回STATUS_PORT_ALREADY_SET NtDebugActiveProcess()的失败的根本原因在于内核结构EPROCESSDebugPort成员已经被设置过了。

为了附加调试器到受保护的进程,好几个逆向论坛发布的解决方法是在父进程的上下文里调用dernel32!DebugActiveProcessStop()。可以通过附加调试器到父进程,在kernel32!WaitForDebugEvent()内部下断,断下来后,注入一段调用DebugActiveProcessStop(childProcessID)的代码并执行,一旦调用成功,这时就可以附加调试器到受保护的进程了。

6.3 TLS Callbacks

另一个被壳使用的技术就是在实际的入口点代码执行之前执行代码,这是通过使用Thread Local Storage (TLS)回调函数来实现的。壳通过这些回调函数执行调试器检测及解密例程,这样逆向分析人员将无法跟踪这些例程。

TLS回调可以使用诸如pedump之类的PE文件分析工具来识别。如果可执行文件中存在TLS条目,数据条目将会显示出来。

Data directory

EXPORT                        rva:00000000  size:00000000

IMPORT                        rva:00061000  size:000000E0

:::

TLS                               rva:000610E0       size:00000018

:::

IAT                                rva:00000000  size:00000000

DELAY_IMPORT           rva:00000000  size:00000000

COM_DESCRPTR          rva:00000000  size:00000000

unused                           rva:00000000  size:00000000

接着显示TLS条目的实际内容。AddressOfCallBacks成员指向一个以null结尾的回调函数数组。

TLS directory

StartAddressOfRawData:                00000000

EndAddressOfRawData:                 00000000

AddressOfIndex:                           004610F 8

AddressOfCallBacks:                   004610FC

SizeOfZeroFill:                             00000000

Characteristics:                              00000000

在这个例子中,RVA 0x4610fc指向回调函数指针(0x 490f 430x44654e):

默认情况下OllyDbg载入这个例子将会暂停在入口点。由于TLS回调函数是在实际的入口点执行之前被调用的,OllyDbg应该配置一下使其在TLS回调被调用之前中断在实际的loader

可以通过选择选项->调试选项->事件->第一次中断于->系统断点来设置中断于ntdll.dll内的实际loader代码。

脱壳的艺术--6. 高级及其它技术_第2张图片

这样设置以后,OllyDbg将会中断在位于执行TLS回调的ntdll!LdrpRunInitializeRoutines()之前的ntdll!_LdrpInitializeProcess(),这时就可以在回调例程中下断并跟踪了。

关于PE文件格式的更多信息及包括pedump的二进制/源码可以在如下的链接获得:

An In-Depth Look into the Win32 Portable Executable File Format by Matt Pietrek

http://msdn.microsoft.com/msdnmag/issues/02/02/PE/default.aspx

An In-Depth Look into the Win32 Portable Executable File Format,Part 2 by Matt Pietrek

http://msdn.microsoft.com/msdnmag/issues/02/03/PE2/

最新版本的微软PE文件格式可以通过如下链接获得:

Microsoft Portable Executable and Common Object File Format Specification

http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx

6.4 Stolen Bytes

代码抽取基本上就是壳移走受保护程序的一部分(通常是入口点的少量指令),这部分指令被复制并在分配的内存中执行。这在某种程度上保护了程序,因为如果从内存中dump受保护进程,被抽取的指令将不会被恢复。

这是一个可执行文件的原始入口点代码:

004011CB        MOV EAX,DWORD PTR FS:[0]

004011D1         PUSH EBP

004011D2         MOV EBP,ESP

004011D4         PUSH -1

004011D6         PUSH 0047401C

004011DB        PUSH 0040109A

004011E0         PUSH EAX

004011E1         MOV DWORD PTR FS:[0],ESP

004011E8         SUB ESP,10

004011EB        PUSH EBX

004011EC        PUSH ESI

004011ED        PUSH EDI

下面是被Enigma加密壳偷取了前两个指令的同一段代码:

004011CB        POP EBX

004011CC        CMP EBX,EBX

004011CE        DEC ESP

004011CF        POP ES

004011D0         JECXZ SHORT 00401169

004011D2         MOV EBP,ESP

004011D4         PUSH -1

004011D6         PUSH 0047401C

004011DB        PUSH 0040109A

004011E0         PUSH EAX

004011E1         MOV DWORD PTR FS:[0],ESP

004011E8         SUB ESP,10

004011EB        PUSH EBX

004011EC        PUSH ESI

004011ED        PUSH EDI

这是被ASProtect壳偷取了数条指令的相同例子。它增加了一条jump指令,指向内存中一段执行被偷代码的过程,被偷的指令和垃圾代码搀杂在一起,想要恢复被偷的代码困难重重。

004011CB        JMP 00B70361

004011D0         JNO SHORT 00401198

004011D3         INC EBX

004011D4         ADC AL ,0B3

004011D6         JL SHORT 00401196

004011D8         INT1

004011D9         LAHF

004011DA        PUSHFD

004011DB        MOV EBX,1D 0F 0294

004011E0         PUSH ES

004011E1         MOV EBX,A 732F 973

004011E6         ADC BYTE PTR DS:[EDX-E],CH

004011E9         MOV ECX,EBP

004011EB        DAS

004011EC        DAA

004011ED        AND DWORD PTR DS:[EBX+58BA76D7],ECX

6.5 API Redirection

API重定向是用来防止逆向分析人员轻易重建受保护程序输入表的一种方法。原始的输入表被销毁,对API的调用被重定向到位于内存中的例程,然后由这些例程负责调用实际的API

在这个例子中代码调用了kernel32!CopyFileA() API

00404F 05         LEA EDI,DWORD PTR SS:[EBP -20C ]

00404FOB        PUSH EDI

00404FOC        PUSH DWORD PTR SS:[EBP-210]

00404F 12         CALL <JMP.&KERNEL32.CopyFileA>

被调用的代码是一个JMP指令,跳转到输入表中的函数地址。

004056B8         JMP DWORD PTR DS:[<&KERNEL32.CopyFileA>]

然而当ASProtect壳重定向KERNEL32!CopyFileA() API时,这段代码被修改为一个call指令,调用壳自己分配的内存中的过程。

004056B8         CALL 00D90000

下图说明了被偷的指令是如何被安置的。前7KERNEL32!CopyFileA()代码中的指令被复制过来,另外0x 7C 83005E Call指令指向的代码也被复制过来。通过一个RETN指令,将控制移交回kernel32.dll领空KERNEL32!CopyFileA()中间的0x 7C 830063地址处:

有些壳则更进一步将整个DLL映像载入到一段分配的内存中,然后重定向API调用到这些DLL映像的拷贝。 这个技术使得在实际的API中下断点变难了。

6.6 Multi-Threaded Packers

对于多线程壳,另一个线程常常用于执行一些诸如解密受保护程序这样必需的操作。多线程壳复杂度增加了,由于跟踪代码变得复杂,理解代码的难度也大大增加了。

PECrypt是一款多线程壳壳,它用第2个线程来解密数据,然后这些数据被主线程使用,这些线程之间通过事件对象进行同步。

PECrypt壳操作并同步线程:

 

6.7 Virtual Machines

使用虚拟机的概念很简单:逆向分析人员最终会想出如何躲过/解决反调试和反逆向技术,当受保护的程序最终需要在内存中解密并执行时,面对静态分析就显得脆弱不堪了。

随着虚拟机的出现,受保护部分的代码被转换成了p-codep-code在执行时可以转换成机器码。原始的机器指令被替换,理解代码所作所为的复杂度成指数上升。

下面是这个概念的简单图示:

Oreans technologiesCodeVirtualizerStraForce这些最新的壳都应用了虚拟机的概念来保护程序。

对付虚拟机需要分析p-code是如果组织并被虚拟机转换的,尽管这一切并不简单。获得足够的信息之后,就可以开发一款反编译引擎来分析P-code并将它们转换成机器码或者是可理解的指令。

一个开发p-code反编译引擎的例子和虚拟机实现的详细信息可以通过如下链接获得:

Defeating HyperUnpackMe2 With an IDA Processor Module, Rolf Rolles III

http://www.openrce.org/articles/full_view/28

 

你可能感兴趣的:(脱壳的艺术--6. 高级及其它技术)