收藏 分享(赏)

脱壳的艺术.doc

上传人:scg750829 文档编号:7475752 上传时间:2019-05-19 格式:DOC 页数:38 大小:693.50KB
下载 相关 举报
脱壳的艺术.doc_第1页
第1页 / 共38页
脱壳的艺术.doc_第2页
第2页 / 共38页
脱壳的艺术.doc_第3页
第3页 / 共38页
脱壳的艺术.doc_第4页
第4页 / 共38页
脱壳的艺术.doc_第5页
第5页 / 共38页
点击查看更多>>
资源描述

1、脱壳的艺术Mark Vincent Yason概述:脱壳是门艺术脱壳既是一种心理挑战,同时也是逆向领域最为激动人心的智力游戏之一。为了甄别或解决非常难的反逆向技巧,逆向分析人员有时不得不了解操作系统的一些底层知识,聪明和耐心也是成功脱壳的关键。这个挑战既牵涉到壳的创建者,也牵涉到那些决心躲过这些保护的脱壳者。本文主要目的是介绍壳常用的反逆向技术,同时也探讨了可以用来躲过或禁用这些保护的技术及公开可用的工具。这些信息将使研究人员特别是恶意代码分析人员在分析加壳的恶意代码时能识别出这些技术,当这些反逆向技术阻碍其成功分析时能决定下一步的动作。第二个目的,这里介绍的信息也会被那些计划在软件中添加一些

2、保护措施用来减缓逆向分析人员分析其受保护代码的速度的研究人员用到。当然没有什么能使一个熟练的、消息灵通的、坚定的逆向分析人员止步的。关键词:逆向工程、壳、保护、反调试、反逆向1 简介 在逆向工程领域,壳是最有趣的谜题之一。在解谜的过程中,逆向分析人员会获得许多关于系统底层、逆向技巧等知识。壳(这个术语在本文中既指压缩壳也包括加密壳)是用来防止程序被分析的。它们被商业软件合法地用于防止信息披露、篡改及盗版。可惜恶意软件也基于同样的理由在使用壳,只不过动机不良。由于大量恶意软件存在加壳现象,研究人员和恶意代码分析人员为了分析代码,开始学习脱壳的技巧。但是随着时间的推移,为防止逆向分析人员分析受保护

3、的程序并成功脱壳,新的反逆向技术也被不断地添加到壳中。并且战斗还在继续,新的反逆向技术被开发的同时逆向分析人员也在针锋相对地发掘技巧、研究技术并开发工具来对付它们。本文主要关注于介绍壳所使用的反逆向技术,同时也探讨了躲过/禁用这些保护措施的工具及技术。可能有些壳通过抓取进程映像(dump)能够轻易被搞定,这时处理反逆向技术似乎没有必要,但是有些情况下加密壳的代码需要加以跟踪和分析,例如:需要躲过部分加密壳代码以便抓取进程映像、让输入表重建工具正确地工作。深入分析加密壳代码以便在一个反病毒产品中整合进脱壳支持。此外,当反逆向技术被恶意程序直接应用,以防止跟踪并分析其恶意行为时,熟悉反逆向技术也是

4、很有价值的。本文绝不是一个完整的反逆向技术的清单,因为它只涵盖了壳中常用的、有趣的一些技术。建议读者参阅最后一节的链接和图书资料,以了解更多其他逆向及反逆向的技术。笔者希望您觉得这些材料有用,并能应用其中的技术。脱壳快乐!2 调试器检测技术 本节列出了壳用来确定进程是否被调试或者系统内是否有调试器正在运行的技术。这些调试器检测技术既有非常简单(明显)的检查,也有涉及到 native APIs 和内核对象的。2.1 PEB.BeingDebugged Flag : IsDebuggerPresent()最基本的调试器检测技术就是检测进程环境块(PEB) 1 中的 BeingDebugged 标志

5、。kernel32!IsDebuggerPresent() API 检查这个标志以确定进程是否正在被用户模式的调试器调试。下面显示了 IsDebuggerPresent() API 的实现代码。首先访问线程环境块(TEB) 2 得到 PEB的地址,然后检查 PEB 偏移 0x02 位置的 BeingDebugged 标志。mov eax, large fs: 18hmov eax, eax+30hmovzx eax, byte ptr eax+2retn除了直接调用 IsDebuggerPresent(),有些壳会手工检查 PEB 中的 BeingDebugged 标志以防逆向分析人员在这个

6、API 上设置断点或打补丁。示例下面是调用 IsDebuggerPresent() API 和使用 PEB.BeingDebugged 标志确定调试器是否存在的示例代码。;call kernel32!IsDebuggerPresent()call IsDebuggerPresenttest eax,eaxjnz .debugger_found;check PEB.BeingDebugged directlyMov eax,dword fs:0x30 ;EAX = TEB.ProcessEnvironmentBlockmovzx eax,byte eax+0x02 ;AL = PEB.Being

7、Debuggedtest eax,eaxjnz .debugger_found由于这些检查很明显,壳一般都会用后面章节将会讨论的垃圾代码或者反反编译技术进行混淆。对策人工将 PEB.BeingDebugged 标志置 0 可轻易躲过这个检测。在数据窗口中 Ctrl+G(前往表达式)输入 fs:30,可以在 OllyDbg 中查看 PEB 数据。另外 Ollyscript 命令“dbh“可以补丁这个标志。dbh最后,Olly Advanced3 插件有置 BeingDebugged 标志为 0 的选项。2.2 PEB.NtGlobalFlag , Heap.HeapFlags, Heap.For

8、ceFlagsPEB.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.Nt

9、GlobalFlag的默认值可以通过 gflags.exe 工具或者在注册表以下位置创建条目来修改:HKLMSoftwareMicrosoftWindows NtCurrentVersionImage File Execution OptionsHeap Flags 由于 NtGlobalFlag 标志的设置,堆也会打开几个标志,这个变化可以在ntdll!RtlCreateHeap()里观测到。通常情况下为进程创建的第一个堆会将其 Flags 和ForceFlags4 分别设为 0x02(HEAP_GROWABLE)和 0 。然而当进程被调试时,这两个标志通常被设为 0x50000062(取决

10、于 NtGlobalFlag)和 0x40000060(等于 Flags AND 0x6001007D) 。默认情况下当一个被调试的进程创建堆时下列附加的堆标志将被设置:HEAP_TAIL_CHECKING_ENABLED(0X20)HEAP_FREE_CHECKING_ENABLED(0X40)示例下面的示例代码检查 PEB.NtGlobalFlag 是否等于 0,为进程创建的第一个堆是否设置了附加标志(PEB.ProcessHeap):;ebx = PEBMov ebx,fs:0x30;Check if PEB.NtGlobalFlag != 0Cmp dword ebx+0x68,0jn

11、e .debugger_found;eax = PEB.ProcessHeapMov eax,ebx+0x18;Check PEB.ProcessHeap.FlagsCmp dword eax+0x0c,2jne .debugger_found;Check PEB.ProcessHeap.ForceFlagsCmp dword eax+0x10,0jne .debugger_found对策可以将 PEB.NtGlobalFlag 和 PEB.HeapProcess 标志补丁为进程未被调试时的相应值。下面是一个补丁上述标志的 ollyscript 示例:Var pebvar patch_addr

12、var process_heap/retrieve PEB via a hardcoded TEB address( first thread: 0x7ffde000)Mov peb,7ffde000+30/patch PEB.NtGlobalFlagLea patch_addr,peb+68mov patch_addr,0/patch PEB.ProcessHeap.Flags/ForceFlagsMov process_heap,peb+18lea patch_addr,process_heap+0cmov patch_addr,2lea patch_addr,process_heap+1

13、0mov patch_addr,0同样地 Olly Advanced 插件有设置 PEB.NtGlobalFlag 和 PEB.ProcessHeap 的选项。2.3 DebugPort: CheckRemoteDebuggerPresent()/NtQueryInformationProcess()Kernel32!CheckRemoteDebuggerPresent()是另一个可以用于确定是否有调试器被附加到进程的 API。这个 API 内部调用了 ntdll!NtQueryInformationProcess(),调用时ProcessInformationclass 参数为 Proces

14、sDebugPort(7)。而 NtQueryInformationProcess()检索内核结构 EPROCESS5 的 DebugPort 成员。非 0 的 DebugPort 成员意味着进程正在被用户模式的调试器调试。如果是这样的话,ProcessInformation 将被置为 0xFFFFFFFF ,否则ProcessInformation 将被置为 0。Kernel32!CheckRemoteDebuggerPresent()接受 2 个参数,第 1 个参数是进程句柄,第 2个参数是一个指向 boolean 变量的指针,如果进程被调试,该变量将包含 TRUE 返回值。BOOL Ch

15、eckRemoteDebuggerPresent(HANDLE hProcess,PBOOL pbDebuggerPresent)ntdll!NtQueryInformationProcess()有 5 个参数。为了检测调试器的存在,需要将ProcessInformationclass 参数设为 ProcessDebugPort(7):NTSTATUS NTAPI NtQueryInformationProcess(HANDLE ProcessHandle,PROCESSINFOCLASS ProcessInformationClass,PVOID ProcessInformation,ULO

16、NG ProcessInformationLength,PULONG ReturnLength)示例下面的例子显示了如何调用 CheckRemoteDebuggerPresent()和NtQueryInformationProcess()来检测当前进程是否被调试:; using Kernel32!CheckRemoteDebuggerPresent()lea eax,.bDebuggerPresentpush eax ;pbDebuggerPresentpush 0xffffffff ;hProcesscall CheckRemoteDebuggerPresentcmp dword .bDeb

17、uggerPresent,0jne .debugger_found; using ntdll!NtQueryInformationProcess(ProcessDebugPort)lea eax,.dwReturnLenpush eax ;ReturnLengthpush 4 ;ProcessInformationLengthlea eax,.dwDebugPortpush eax ;ProcessInformationpush ProcessDebugPort ;ProcessInformationClass(7)push 0xffffffff ;ProcessHandlecall NtQu

18、eryInformationProcesscmp dword .dwDebugPort,0jne .debugger_found对策一种方法是在 NtQueryInformationProcess()返回的地方设置断点,当这个断点被断下来后,将 ProcessInformation 补丁为 0。 下面是自动执行这个方法的 ollyscript 示例:var bp_NtQueryInformationProcess/ set a breakpoint handlereob bp_handler_NtQueryInformationProcess/ set a breakpoint where N

19、tQueryInformationProcess returnsgpa “NtQueryInformationProcess“,“ntdll.dll“find $RESULT,#C21400# /retn 14mov bp_NtQueryInformationProcess,$RESULTbphws bp_NtQueryInformationProcess,“X“runbp_handler_NtQueryInformationProcess:/ProcessInformationClass = ProcessDebugPort?cmp esp+8,7jne bp_handler_NtQuery

20、InformationProcess_continue/patch ProcessInformation to 0mov patch_addr,esp+cmov patch_addr,0/ clear breakpointbphwc bp_NtQueryInformationProcessbp_handler_NtQueryInformationProcess_continue:runOlly Advanced 插件有一个 patch NtQueryInformationProcess()的选项,这个补丁涉及注入一段代码来操纵 NtQueryInformationProcess()的返回值。2

21、.4 Debugger Interrupts在调试器中步过 INT3 和 INT1 指令的时候,由于调试器通常会处理这些调试中断,所以异常处理例程默认情况下将不会被调用,Debugger Interrupts 就利用了这个事实。这样壳可以在异常处理例程中设置标志,通过 INT 指令后如果这些标志没有被设置则意味着进程正在被调试。另外,kernel32!DebugBreak() 内部是调用了 INT3 来实现的,有些壳也会使用这个 API。示例这个例子在异常处理例程中设置 EAX 的值为 0xFFFFFFFF(通过 CONTEXT6 记录)以此来判断异常处理例程是否被调用:; set excep

22、tion handlerpush .exeception_handlerpush dword fs:0mov fs:0,esp;reset flag(EAX) invoke int3xor eax,eaxint3;restore exception handlerpop dword fs:0add esp,4; check if the flag had been settest eax,eaxje .debugger_found:.exeception_handler:;EAX = ContextRecordmov eax,esp+0xc;set flag (ContextRecord.EA

23、X)mov dword eax+0xb0,0xffffffff;set ContextRecord.EIPinc dword eax+0xb8xor eax,eaxretn对策由于调试中断而导致执行停止时,在 OllyDbg 中识别出异常处理例程(通过视图-SEH链)并下断点,然后 Shift+F9 将调试中断/ 异常传递给异常处理例程,最终异常处理例程中的断点会断下来,这时就可以跟踪了。另一个方法是允许调试中断自动地传递给异常处理例程。在 OllyDbg 中可以通过 选项- 调试选项 - 异常 - 忽略下列异常 选项卡中钩选“INT3 中断“ 和“ 单步中断“复选框来完成设置。2.5 Tim

24、ing Checks当进程被调试时,调试器事件处理代码、步过指令等将占用 CPU 循环。如果相邻指令之间所花费的时间如果大大超出常规,就意味着进程很可能是在被调试,而壳正好利用了这一点。示例下面是一个简单的时间检查的例子。在某一段指令的前后用 RDTSC 指令(Read Time-Stamp Counter)并计算相应的增量。增量值 0x200 取决于两个 RDTSC 指令之间的代码执行量。rdtscmov ecx,eaxmov ebx,edx;.more instructionsnoppush eaxpop eaxnop;.more instructions;compute delta be

25、tween RDTSC instructionsrdtsc;Check high order bitscmp edx,ebxja .debugger_found;Check low order bitssub eax,ecxcmp eax,0x200ja .debugger_found其它的时间检查手段包括使用 kernel32!GetTickCount() API, 或者手工检查位于0x7FFE0000 地址的 SharedUserData7 数据结构的 TickCountLow 及 TickCountMultiplier 成员。使用垃圾代码或者其它混淆技术进行隐藏以后,这些时间检查手段尤其

26、是使用 RDTSC将会变得难于识别。对策一种方法就是找出时间检查代码的确切位置,避免步过这些代码。逆向分析人员可以在增量比较代码之前下断然后用 运行 代替 步过 直到断点断下来。另外也可以下GetTickCount()断点以确定这个 API 在什么地方被调用或者用来修改其返回值。Olly Advanced 采用另一种方法它安装了一个内核模式驱动程序做以下工作:1 设置控制寄存器 CR48 中的时间戳禁止位(TSD) ,当这个位被设置后如果RDTSC 指令在非 Ring0 下执行将会触发一个通用保护异常(GP) 。2 中断描述表( IDT)被设置以挂钩 GP 异常并且 RTDSC 的执行被过滤。

27、如果是由于 RDTSC 指令引发的 GP,那么仅仅将前次调用返回的时间戳加 1。值得注意的是上面讨论的驱动可能会导致系统不稳定,应该始终在非生产机器或虚拟机中进行尝试。2.6 SeDebugPrivilege默认情况下进程是没有 SeDebugPrivilege 权限的。然而进程通过 OllyDbg 和 WinDbg 之类的调试器载入的时候,SeDebugPrivilege 权限被启用了。这种情况是由于调试器本身会调整并启用 SeDebugPrivilege 权限,当被调试进程加载时 SeDebugPrivilege 权限也被继承了。一些壳通过打开 CSRSS.EXE 进程间接地使用 SeDe

28、bugPrivilege 确定进程是否被调试。如果能够打开 CSRSS.EXE 意味着进程启用了 SeDebugPrivilege 权限,由此可以推断进程正在被调试。这个检查能起作用是因为 CSRSS.EXE 进程安全描述符只允许 SYSTEM 访问,但是一旦进程拥有了 SeDebugPrivilege 权限,就可以忽视安全描述符 9 而访问其它进程。注意默认情况下这一权限仅仅授予了 Administrators 组的成员。示例下面是 SeDebugPrivilege 检查的例子:;query for the PID of CSRSS.EXEcall CsrGetProcessId;try t

29、o open the CSRSS.EXE processpush eaxpush FALSEpush PROCESS_QUERY_INFORMATIONcall OpenProcess;if OpenProcess() was successful,;process is probably being debuggedtest eax,eaxjnz .debugger_found这里使用了 ntdll!CsrGetProcessId() API 获取 CSRSS.EXE 的 PID,但是壳也可能通过手工枚举进程来得到 CSRSS.EXE 的 PID。如果 OpenProcess()成功则意味着

30、 SeDebugPrivilege权限被启用,这也意味着进程很可能被调试。对策一种方法是在 ntdll!NtOpenProcess()返回的地方设断点,一旦断下来后,如果传入的是CSRSS.EXE 的 PID 则修改 EAX 值为 0xC0000022(STATUS_ACCESS_DENIED)。2.7 Parent Process(检测父进程)通常进程的父进程是 explorer.exe(双击执行的情况下) ,父进程不是 explorer.exe 说明程序是由另一个不同的应用程序打开的,这很可能就是程序被调试了。下面是实现这种检查的一种方法:1 通过 TEB(TEB.ClientId)或者使

31、用 GetCurrentProcessId()来检索当前进程的 PID2 用 Process32First/Next()得到所有进程的列表,注意 explorer.exe 的 PID(通过PROCESSENTRY32.szExeFile)和通过 PROCESSENTRY32.th32ParentProcessID 获得的当前进程的父进程 PID3 如果父进程的 PID 不是 explorer.exe 的 PID,则目标进程很可能被调试但是请注意当通过命令行提示符或默认外壳非 explorer.exe 的情况下启动可执行程序时,这个调试器检查会引起误报。对策Olly Advanced 提供的方法

32、是让 Process32Next()总是返回 fail,这样壳的进程枚举代码将会失效,由于进程枚举失效 PID 检查将会被跳过。这些是通过补丁 kernel32!Process32NextW()的入口代码(将 EAX 值设为 0 然后直接返回)实现的。77E8D1C2 33C0 xor eax, eax77E8D1C4 C3 retn77E8D1C5 83EC 0C sub esp, 0C2.8 DebugObject: NtQueryObject()除了识别进程是否被调试之外,其他的调试器检测技术牵涉到检查系统当中是否有调试器正在运行。逆向论坛中讨论的一个有趣的方法就是检查 DebugObj

33、ect10 类型内核对象的数量。这种方法之所以有效是因为每当一个应用程序被调试的时候,将会为调试对话在内核中创建一个 DebugObject 类型的对象。DebugObject 的数量可以通过 ntdll!NtQueryObject()检索所有对象类型的信息而获得。NtQueryObject 接受 5 个参数,为了查询所有的对象类型,ObjectHandle 参数被设为NULL,ObjectInformationClass 参数设为 ObjectAllTypeInformation(3):NTSTATUS NTAPI NtQueryObject(HANDLE ObjectHandle,OBJE

34、CT_INFORMATION_CLASS ObjectInformationClass,PVOID ObjectInformation,ULONG Length,PULONG ResultLength)这个 API 返回一个 OBJECT_ALL_INFORMATION 结构,其中 NumberOfObjectsTypes成员为所有的对象类型在 ObjectTypeInformation 数组中的计数:typedef struct _OBJECT_ALL_INFORMATIONULONG NumberOfObjectsTypes;OBJECT_TYPE_INFORMATION ObjectTy

35、peInformation1;检测例程将遍历拥有如下结构的 ObjectTypeInformation 数组:typedef struct _OBJECT_TYPE_INFORMATION00 UNICODE_STRING TypeName;08 ULONG TotalNumberofHandles;0C ULONG TotalNumberofObjects;.more fields.TypeName 成员与 UNICODE 字符串“DebugObject“比较,然后检查TotalNumberofObjects 或 TotalNumberofHandles 是否为非 0 值。对策与 NtQue

36、ryInformationProcess()解决方法类似,在 NtQueryObject()返回处设断点,然后补丁 返回的 OBJECT_ALL_INFORMATION 结构,另外 NumberOfObjectsTypes 成员可以置为 0 以防止壳遍历 ObjectTypeInformation 数组。可以通过创建一个类似于NtQueryInformationProcess()解决方法的 ollyscript 脚本来执行这个操作。类似地,Olly Advanced 插件向 NtQueryObject() API 中注入代码,如果检索的是ObjectAllTypeInformation 类型则

37、用 0 清空整个返回的缓冲区。2.9 Debugger Window调试器窗口的存在标志着有调试器正在系统内运行。由于调试器创建的窗口拥有特定类名(OllyDbg 的是 OLLYDBG,WinDbg 的是 WinDbgFrameClass) ,使用user32!FindWindow()或者 user32!FindWindowEx()能很容易地识别这些调试器窗口。示例下面的示例代码使用 FindWindow()查找 OllyDbg 或 WinDbg 创建的窗口来识别他们是否正在系统中运行。push NULLpush .szWindowClassOllyDbgcall FindWindowAtes

38、t eax,eaxjnz .debugger_foundpush NULLpush .szWindowClassWinDbgcall FindWindowAtest eax,eaxjnz .debugger_found.szWindowClassOllyDbg db “OLLYDBG”,0.szWindowClassWinDbg db “WinDbgFrameClass”,0对策一种方法是在 FindWindow() /FindWindowEx()的入口处设断点,断下来后,改变lpClassName 参数的内容,这样 API 将会返回 fail,另一种方法就是直接将返回值设为NULL。2.10

39、 Debugger Process另外一种识别系统内是否有调试器正在运行的方法是列出所有的进程,检查进程名是否与调试器(如 OLLYDBG.EXE,windbg.exe 等)的相符。实现很直接,利用Process32First/Next()然后检查映像名称是否与调试器相符就行了。有些壳也会利用 kernel32!ReadProcessMemory()读取进程的内存,然后寻找调试器相关的字符串(如”OLLYDBG”)以防止逆向分析人员修改调试器的可执行文件名。一旦发现调试器的存在,壳要么显示一条错误信息,要么默默地退出或者终止调试器进程。对策和父进程检查类似,可以通过补丁 kernel32!Pr

40、ocess32NextW() 使其总是返回 fail 值来防止壳枚举进程。2.11 Device Drivers检测内核模式的调试器是否活跃于系统中的典型技术是访问他们的设备驱动程序。该技术相当简单,仅涉及调用 kernel32!CreateFile()检测内核模式调试器(如 SoftICE)使用的那些众所周知的设备名称。示例一个简单的检查如下:push NULLpush 0push OPEN_EXISTINGpush NULLpush FILE_SHARE_READpush GENERIC_READpush .szDeviceNameNticecall CreateFileAcmp eax,

41、INVALID_HANDLE_VALUEjne .debugger_found.szDeviceNameNtice db “.NTICE“,0某些版本的 SoftICE 会在设备名称后附加数字导致这种检查失败,逆向论坛中相关的描述是穷举附加的数字直到发现正确的设备名称。新版壳也用设备驱动检测技术检测诸如Regmon 和 Filemon 之类的系统监视程序的存在。对策一种简单的方法就是在 kernel32!CreateFileW()内设置断点,断下来后,要么操纵FileName 参数要么改变其返回值为 INVALID_HANDLE_VALUE(0xFFFFFFFF) 。2.12 OllyDbg:

42、 Guard Pages这个检查是针对 OllyDbg 的,因为它和 OllyDbg 的内存访问/写入断点特性相关。除了硬件断点和软件断点外,OllyDbg 允许设置一个内存访问/写入断点,这种类型的断点是通过页面保护 11 来实现的。简单地说,页面保护提供了当应用程序的某块内存被访问时获得通知这样一个途径。页面保护是通过 PAGE_GUARD 页面保护修改符来设置的,如果访问的内存地址是受保护页面的一部分,将会产生一个 STATUS_GUARD_PAGE_VIOLATION(0x80000001)异常。如果进程被 OllyDbg 调试并且受保护的页面被访问,将不会抛出异常,访问将会被当作内存

43、断点来处理,而壳正好利用了这一点。示例下面的示例代码中,将会分配一段内存,并将待执行的代码保存在分配的内存中,然后启用页面的 PAGE_GUARD 属性。接着初始化标设符 EAX 为 0,然后通过执行内存中的代码来引发 STATUS_GUARD_PAGE_VIOLATION 异常。如果代码在 OllyDbg 中被调试,因为异常处理例程不会被调用所以标设符将不会改变。;set up exception handlerpush .exception_handlepush dword fs:0mov fs:0,esp;allocate memorypush PAGE_READWRITEpush ME

44、M_COMMITpush 0x1000push NULLcall VirtualAlloctest eax,eaxjz .failedmov .pAllocatedMem,eax;store a RETN on the allocated memorymov byte eax,0xC3;then set the PAGE_GUARD attribute of the allocated memorylea eax,.dwOldProtectpush eaxpush PAGE_EXECUTE_READ | PAGE_GUARDpush 0x1000push dword .pAllocatedMe

45、mcall VirtualProtect;set marker (EAX) as 0xor eax,eax;trigger a STATUS_GUARD_PAGE_VIOLATION exceptioncall .pAllocatedMem;check if marker had not been changed (exception handler not called)test eax,eaxje .debugger_found.exception_handler;EAX = CONTEXT recordmov eax,esp+0xC;set marker (CONTEXT.EAX) to

46、 0xFFFFFFFF;to signal that the exception handler was calledmov dword eax+0xb0,0xFFFFFFFFxor eax,eaxretn对策由于页面保护引发一个异常,逆向分析人员可以故意引发一个异常,这样异常处理例程将会被调用。在示例中,逆向分析人员可以用 INT3 指令替换掉 RETN 指令,一旦 INT3指令被执行,Shift+F9 强制调试器执行异常处理代码。这样当异常处理例程调用后,EAX将被设为正确的值,然后 RETN 指令将会被执行。如果异常处理例程里检查异常是否真地是 STATUS_GUARD_PAGE_VIO

47、LATION,逆向分析人员可以在异常处理例程中下断点然后修改传入的 ExceptionRecord 参数,具体来说就是 ExceptionCode, 手工将 ExceptionCode 设为 STATUS_GUARD_PAGE_VIOLATION即可。3 断点和补丁检测技术 本节列举了壳最常用的识别软件断点、硬件断点和补丁的方法。3.1 Software Breakpoint Detection 软件断点是通过修改目标地址代码为 0xCC(INT3/Breakpoint Interrupt )来设置的断点。壳通过在受保护的代码段和(或)API 函数中扫描字节 0xCC 来识别软件断点。示例检测

48、可能和下面一样简单:cldmov edi,Protected_Code_Startmov ecx,Protected_Code_End - Protected_Code_Startmov al,0xccrepne scasbjz .breakpoint_found有些壳对比较的字节值作了些运算使得检测变得不明显,例如:if ( byte XOR 0x55 = 0x99 ) then breakpoint foundWhere: 0x99 = 0xCC XOR 0x55对策如果软件断点被发现了逆向分析人员可以使用硬件断点来代替。如果需要在 API 内部下断,但是壳又检测 API 内部的断点,逆向

49、分析人员可以在最终被 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 结构可以

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报