详解C++的反调试技术与绕过手法
反调试技术的实现方式有很多最简单的一种实现方式莫过于直接调用Windows系统提供给我们的API函数这些API函数中有些专门用来检测调试器的有些则是可被改造为用于探测调试器是否存在的工具多数情况下调用系统API函数实现反调试是不明智的原因很简单目标主机通常会安装主动防御系统而作为主动防御产品默认会加载RootKit驱动挂钩这些敏感函数的使用如果被非法调用则会提示错误信息病毒作者通常会使用汇编自行实现这些类似于系统提供给我们的反调试函数并不会使用系统的API这样依附于API的主动防御的系统将会失效。1.加载调试符号链接文件并放入d:/symbols目录下.1230:000 .sympath srv*d:\symbols*http://msdl.microsoft.com/download/symbols0:000 .reloadReloading current modules2.位于fs:[0x30]的位置就是PEB结构的指针,接着我们分析如何得到的该指针,并通过通配符找到TEB结构的名称.1234560:000 dt ntdll!*teb*ntdll!_TEBntdll!_GDI_TEB_BATCHntdll!_TEB_ACTIVE_FRAMEntdll!_TEB_ACTIVE_FRAME_CONTEXTntdll!_TEB_ACTIVE_FRAME_CONTEXT3.接着可通过dt命令,查询下ntdll!_TEB结构,如下可看到0x30处ProcessEnvironmentBlock存放的正是PEB结构.12345670:000 dt -rv ntdll!_TEBstruct _TEB, 66 elements, 0xfb8 bytes0x000 NtTib : struct _NT_TIB, 8 elements, 0x1c bytes# NT_TIB结构0x018 Self : Ptr32 to struct _NT_TIB, 8 elements, 0x1c bytes# NT_TIB结构0x020 ClientId : struct _CLIENT_ID, 2 elements, 0x8 bytes# 保存进程与线程ID0x02c ThreadLocalStoragePointer : Ptr32 to Void0x030 ProcessEnvironmentBlock : Ptr32 to struct _PEB, 65 elements, 0x210 bytes# PEB结构偏移地址0x18是_NT_TIB结构,也就是指向自身偏移0x0的位置.1234560:000 r $teb$teb7ffdf0000:000dd$teb0x187ffdf018 7ffdf000 00000000 00001320 00000c107ffdf028 00000000 00000000 7ffd9000 00000000而!teb地址加0x30正是PEB的位置,可以使用如下命令验证.1234567891011121314151617180:000dd$teb0x307ffdf030 7ffd9000 00000000 00000000 000000007ffdf040 00000000 00000000 00000000 000000000:000 !tebTEB at 7ffdf000ExceptionList: 0012fd0cStackBase: 00130000StackLimit: 0012e000SubSystemTib: 00000000FiberData: 00001e00ArbitraryUserPointer: 00000000Self: 7ffdf000EnvironmentPointer: 00000000ClientId: 00001320 . 00000c10RpcHandle: 00000000Tls Storage: 00000000PEB Address: 7ffd9000# 此处teb地址上方的查询结果可得知偏移位置fs:[0x18]正是TEB的基址TEB:7ffdf00012345678910111213140:000ddfs:[0x18]003b:00000018 7ffdf000 00000000 000010f4 00000f6c003b:00000028 00000000 00000000 7ffda000 000000000:000 dt _teb 0x7ffdf000ntdll!_TEB0x000 NtTib : _NT_TIB0x01c EnvironmentPointer : (null)0x020 ClientId : _CLIENT_ID# 这里保存进程与线程信息0:000 dt _CLIENT_ID 0x7ffdf000# 查看进程详细结构ntdll!_CLIENT_ID0x000 UniqueProcess : 0x0012fd0c Void# 获取进程PID0x004 UniqueThread : 0x00130000 Void# 获取线程PID上方TEB首地址我们知道是fs:[0x18],接着我们通过以下公式计算得出本进程的进程ID.在Windows系统中如果想要获取到PID进程号,可以使用NtCurrentTeb()这个系统API来实现,但这里我们手动实现该API的获取过程.获取进程PID:1234567891011121314151617181920#include stdafx.h#include Windows.hDWORDGetPid(){DWORDdwPid0;__asm{mov eax,fs:[0x18]// 获取PEB地址add eax,0x20// 加0x20得到进程PIDmov eax,[eax]mov dwPid,eax}returndwPid;}intmain(){printf(%d\n,GetPid());return0;}获取线程PID:123456789101112131415161718192021#include stdafx.h#include Windows.hDWORDGetPid(){DWORDdwPid0;__asm{mov eax,fs:[0x18]// 获取PEB地址add eax,0x20// 加0x20得到进程PIDadd eax,0x04// 加0x04得到线程PIDmov eax,[eax]mov dwPid,eax}returndwPid;}intmain(){printf(%d\n,GetPid());return0;}通过标志反调试下方的调试标志BeingDebugged是Char类型,为1表示调试状态.为0表示没有调试.可以用于反调试.12345670:000 dt _pebntdll!_PEB0x000 InheritedAddressSpace : UChar0x001 ReadImageFileExecOptions : UChar0x002 BeingDebugged : UChar0x003 SpareBool : UChar0x004 Mutant : Ptr32 Void123456789101112131415161718192021222324#include stdafx.h#include Windows.hintmain(){DWORDdwIsDebug 0;__asm{mov eax, fs:[0x18];// 获取TEBmov eax, [eax 0x30];// 获取PEBmovzx eax, [eax 2];// 获取调试标志mov dwIsDebug,eax}if(1 dwIsDebug){printf(正在被调试);}else{printf(没有被调试);}return0;}通过API反调试1234567891011121314151617#include stdio.h#include stdlib.h#include Windows.hintmain(){STARTUPINFO temp;temp.cb sizeof(temp);GetStartupInfo(temp);if(temp.dwFlags ! 1){ExitProcess(0);}printf(程序没有被反调试);return0;}反调试与绕过思路BeingDebugged 属性反调试:进程运行时位置FS:[30h]指向PEB的基地址为了实现反调试技术恶意代码通过这个位置来检查BeingDebugged标志位是否为1如果为1则说明进程被调试。1.首先我们可以使用 dt _teb 命令解析一下TEB的结构如下TEB结构的起始偏移为0x0而0x30的位置指向的是 ProcessEnvironmentBlock 也就是指向了进程环境块。1234567891011121314150:000 dt _tebntdll!_TEB0x000 NtTib : _NT_TIB0x01c EnvironmentPointer : Ptr32 Void0x020 ClientId : _CLIENT_ID0x028 ActiveRpcHandle : Ptr32 Void0x02c ThreadLocalStoragePointer : Ptr32 Void0x030 ProcessEnvironmentBlock : Ptr32 _PEB// 此处是进程环境块0x034 LastErrorValue : Uint4B0x038 CountOfOwnedCriticalSections : Uint4B0x03c CsrClientThread : Ptr32 Void0x040 Win32ThreadInfo : Ptr32 Void0x044 User32Reserved : [26] Uint4B0x0ac UserReserved : [5] Uint4B0x0c0 WOW32Reserved : Ptr32 Void只需要在进程环境块的基础上 0x2 就能定位到线程环境块TEB中 BeingDebugged 的标志此处的标志位如果为1则说明程序正在被调试为0则说明没有被调试。123456780:000 dt _pebntdll!_PEB0x000 InheritedAddressSpace : UChar0x001 ReadImageFileExecOptions : UChar0x002 BeingDebugged : UChar0x003 BitField : UChar0x003 ImageUsesLargePages : Pos 0, 1 Bit0x003 IsProtectedProcess : Pos 1, 1 Bit我们手动来验证一下首先线程环境块地址是007f1000在此基础上加0x30即可得到进程环境快的基地址007ee000继续加0x2即可得到BeingDebugged的状态 ffff0401 需要 byte1123456789101112130:000 r $teb$teb007f10000:000 dd 007f1000 0x30007f1030 007ee000 00000000 00000000 00000000007f1040 00000000 00000000 00000000 000000000:000 r $peb$peb007ee0000:000 dd 007ee000 0x2007ee002 ffff0401 0000ffff 0c400112 19f0775f007ee012 0000001b 00000000 09e0001b 0000775f梳理一下知识点我们可以写出一下反调试代码本代码单独运行程序不会出问题一旦被调试器附加则会提示正在被调试。123456789101112131415161718192021222324252627#include stdio.h#include windows.hintmain(){BYTEIsDebug 0;__asm{mov eax, dword ptr fs:[0x30]mov bl, byte ptr [eax 0x2]mov IsDebug, bl}/* 另一种反调试实现方式__asm{push dword ptr fs:[0x30]pop edxmov al, [edx 2]mov IsDebug,al}*/if(IsDebug ! 0)printf(本程序正在被调试. %d, IsDebug);elseprintf(程序没有被调试.);getchar();return0;}复制讲解如果恶意代码中使用该种技术阻碍我们正常调试该如何绕过呢如下我们只需要在命令行中执行dump fs:[30]2来定位到BeingDebugged的位置并将其数值改为0然后运行程序会发现反调试已经被绕过了。