第51章:静态反调试技术
PEB
前面讲过,与反调试相关的 PEB 结构体成员有
①PEB.BeingDebugged(+0x2)
修改该值为0即可
②PEB.Ldr(+0xC)—— 仅适用于 Win XP
调试进程时,其内存区域会出现一些特殊标识,未使用的堆内存区域会全部填充着 0xFEEEFEEE ,PEB.Ldr 指向一个_PEB_LDR_DATA 结构体的指针,并且该结构体正好在堆内存中创建。Win XP 以上皆会填充为 NULL 。
③PEB.Process Heap.xxx(+0x18)—— 仅适用于 Win XP
当进程处于调试状态时,PEB.ProcessHeap.Flags(+0xC)与 PEB.ProcessHeap.ForceFlags(+0x10)被设为特定值。
正常时,值分别为:0x2,0x0;被调试时,值分别为:0x50000062,0x40000060
④PEB.NTGlobalFlag(+0x68)
当处于调试状态时,该成员的的值为0x70,并且该值是由下列 Flags 值 相或 得到
调试
在 StaAD_PEB.exe 中查看:
一直检测 Ldr 所指区域的内存值,直到连续出现 4个Dword 的 FEEEFEEE ,但是,这里并没有跳转到显示非调试器的打印区域。猜测是使用了 SEH ,由于会一直搜索下去,一定会碰到未知区域,触发异常。
如此就组成了 SEH 的第一个结构体。发生异常后,会调用 004019D0 处的代码,在该函数的前面有一段代码不会执行跳转:
此处的 66 是一个特殊值,如果执行 test 的结果使得下面的跳转语句跳转,则此函数执行一系列安全语句后回返回1,表示没有处理该异常,调用下一个异常处理函数。
若不跳转则会执行到:
在函数内部可以看到:
而 call ecx ,会执行:
这个函数其实就是为了返回 eax == 1,然后对 eax 值进行判断,此时其实就已经流入了__except 代码块中,只是在执行用户自定义代码时还需要进行一系列的安全操作和全局展开,并在第二个判断处跳转,跳转再次将 ExceptionFlags 与 E06D7363 进行判断,是否要销毁异常 Object (当然会跳走):
(上图是使用了源代码重新编译之后放入X64dbg 中得到的),接下来执行到:
函数内部:
函数内有一段代码,在执行 sub 指令前,ntdll.7018E18 函数会再次执行调用异常处理函数时的代码:
此处 ECX == 77018E90 ,即:
可以看到,此处会返回 eax == 1,即 ExceptionContinueSearch ,表明此函数不能处理异常,运行下一个异常处理器,接下来:
eax 是1( If ExceptionFlags != 0 or 1),如果 eax != 1,那么会重新抛出一个异常(C0000026 STATUS_INVALID_DISPOSITION):
接下来正常执行了 RtlUnwind 函数后,会正常返回,并且在执行一系列的安全检查后:
其主要作用就是 jmp esi ,修改寄存器的值,然后跳转到 4010F8 。
EXCEPTION_EXECUTE_HANDLER (1) 表明异常已经被识别,也即当前的这个异常错误,系统已经找到了并能够确认,这个__except 模块就是正确的异常处理模块。控制流将进入到__except模块中,当__except代码块运行结束后,系统会认为异常已经处理,于是允许应用程序继续执行。不管是程序自定义异常处理还是系统的异常处理程序,处理完后,__except 比较其返回值与自身的值,决定程序的运行流程。若值为 1,则运行 __except 中的代码,若为 EXCEPTION_CONTINUE_EXECUTION (–1) ,即程序控制流跳转到导致异常的那条指令,并尝试重新执行这条指令,继续恢复运行。
然后继续查看对于 PEB.ProcessHeap.Flags(+C) & ForceFlags(+10h) 的检验:
二者都差不多,接下来是对 PEB.NtGlobalFlag(+0x68)的检验: