26种对付反调试的方法

目前主要有3种分析软件的方法:

10年积累的网站设计、成都网站制作经验,可以快速应对客户对网站的新想法和需求。提供各种问题对应的解决方案。让选择我们的客户得到更好、更有力的网络服务。我虽然不认识你,你也不认识我。但先网站制作后付款的网站建设流程,更有阿合奇免费网站建设让你可以放心的选择与我们合作。

1.数据交换分析,研究人员使用数据包嗅探工具来分析网络数据交换。

2.对软件的二进制代码进行反汇编,然后以汇编语言列出。

3.字节码解码或二进制解码,然后以高级编程语言重新创建源代码。

本文针对的是Windows操作系统中常用的防破解及防逆向工程保护技术,即反调试方法,各种防逆向工程技术的主要目标是尽可能多的使逆变工具尽可能失效。

本文的对付反调试方法,总共涉及26种:

1. IsDebuggerPresent

2. PEB(进程环境块)

3.如何避开IsDebuggerPresent的检查

4.TLS回调

5.NtGlobalFlag

6.如何避开NtGlobalFlag检查

7.NtGlobalFlag和IMAGE_LOAD_CONFIG_DIRECTORY

8.HeapFlag和ForceFlags

9.如何避开HeapFlag和ForceFlags

10.陷阱标识检查

11.如何避开陷阱标识检查

12.CheckRemoteDebuggerPresent和NtQueryInformationProcess

13.如何避开CheckRemoteDebuggerPresent和NtQueryInformationProcess

14.基于NtQueryInformationProcess的其他反调试保护技术

15.如何避开NtQueryInformationProcess检查

16.软件和硬件的断点反应

17.SEH(结构化异常处理)

18.如何避开SHE检查

19.VEH(向量化异常处理)

20.如何避开硬件断点检查和VEH

21.NtSetInformationThread ,在调试工具中隐藏线程

22.如何避开从调试工具中隐藏线程

23.NtCreateThreadEx

24. 如何避开NtCreateThreadEx

25.处理跟踪

26.堆栈段操作

建议你在阅读本文时,先具备一定的Assembler知识,一些Windbg操作经验以及使用API函数开发Windows的经验。

IsDebuggerPresent

也许最简单的方法是调用IsDebuggerPresent函数,用此函数检测用户模式的调试器是否正在调试调用进程。下面的代码就是一个基本的保护案例:

 
 
 
  1. int main() 
  2.     if (IsDebuggerPresent()) 
  3.     { 
  4.         std::cout << "Stop debugging program!" << std::endl; 
  5.         exit(-1); 
  6.     } 
  7.     return 0; 

如果我们来看看IsDebuggerPresent函数,我们会发现这样的代码:

 
 
 
  1. 0:000< u kernelbase!IsDebuggerPresent L3 
  2. KERNELBASE!IsDebuggerPresent: 
  3. 751ca8d0 64a130000000    mov     eax,dword ptr fs:[00000030h] 
  4. 751ca8d6 0fb64002        movzx   eax,byte ptr [eax+2] 
  5. 751ca8da c3              ret 

Windows X64里的进程如下:

 
 
 
  1. 0:000< u kernelbase!IsDebuggerPresent L3 
  2. KERNELBASE!IsDebuggerPresent: 
  3. 00007ffc`ab6c1aa0 65488b042560000000 mov   rax,qword ptr gs:[60h] 
  4. 00007ffc`ab6c1aa9 0fb64002           movzx eax,byte ptr [rax+2] 
  5. 00007ffc`ab6c1aad c3                 ret 

在FS寄存器的偏移量30h处存在PEB(进程环境块),而在X64上,PEB(进程环境块)存在于GS段寄存器的偏移量60h处。在PEB中的2个偏移量处,我们将找到BeingDebugged字段:

 
 
 
  1. 0:000< dt _PEB 
  2. ntdll!_PEB 
  3.    +0x000 InheritedAddressSpace : UChar 
  4.    +0x001 ReadImageFileExecOptions : UChar 
  5.    +0x002 BeingDebugged    : UChar 

即IsDebuggerPresent函数读取BeingDebugged字段的值。如果进程被调试,值为1,否则为0。

PEB(进程环境块)

PEB是在操作系统内使用的封闭结构。在不同地运行环境下,大家应该以不同的方式获取PEB结构指针。如下所示,你可以在下图中找到x32和x64系统的PEB指针:

 
 
 
  1. // Get PEB for WOW64 Process 
  2. PVOID GetPEB64() 
  3.     PVOID pPeb = 0; 
  4. #ifndef _WIN64 
  5.     // 1. There are two copies of PEB - PEB64 and PEB32 in WOW64 process 
  6.     // 2. PEB64 follows after PEB32 
  7.     // 3. This is true for version less then Windows 8, else __readfsdword returns address of real PEB64 
  8.     if (IsWin8OrHigher()) 
  9.     { 
  10.         BOOL isWow64 = FALSE; 
  11.         typedef BOOL(WINAPI *pfnIsWow64Process)(HANDLE hProcess, PBOOL isWow64); 
  12.         pfnIsWow64Process fnIsWow64Process = (pfnIsWow64Process) 
  13.             GetProcAddress(GetModuleHandleA("Kernel32.dll"), "IsWow64Process"); 
  14.         if (fnIsWow64Process(GetCurrentProcess(), &isWow64)) 
  15.         { 
  16.             if (isWow64) 
  17.             { 
  18.                 pPeb = (PVOID)__readfsdword(0x0C * sizeof(PVOID)); 
  19.                 pPeb = (PVOID)((PBYTE)pPeb + 0x1000); 
  20.             } 
  21.         } 
  22.     } 
  23. #endif 
  24.     return pPeb; 

检查操作系统版本的功能代码如下:

 
 
 
  1. WORD GetVersionWord() 
  2.     OSVERSIONINFO verInfo = { sizeof(OSVERSIONINFO) }; 
  3.     GetVersionEx(&verInfo); 
  4.     return MAKEWORD(verInfo.dwMinorVersion, verInfo.dwMajorVersion); 
  5. BOOL IsWin8OrHigher() { return GetVersionWord() >= _WIN32_WINNT_WIN8; } 
  6. BOOL IsVistaOrHigher() { return GetVersionWord() >= _WIN32_WINNT_VISTA; }        

如何避开IsDebuggerPresent检查

为了做到这一点,在执行检查代码之前,需要将0置于BeingDebugged。例如,可以使用DLL注入:

 
 
 
  1. mov eax, dword ptr fs:[0x30]   
  2. mov byte ptr ds:[eax+2], 0 

Windows X64里的进程如下:

 
 
 
  1. DWORD64 dwpeb = __readgsqword(0x60); 
  2. *((PBYTE)(dwpeb + 2)) = 0; 

TLS回调

其实,在主函数中检查调试器的存在不是最好的方法,因为TLS回调处于反汇编列表时反向工具的第一个位置。它实施的检查可以由nop指令擦除,从而解除保护。如果使用CRT库,则在将控制权转移到主函数之前,主线程就已经有一个调用堆栈了。执行调试器存在检查的一个方法便是TLS回调。如下图所示,在可执行模块入口调用之前就已经调用回调函数。

 
 
 
  1. #pragma section(".CRT$XLY", long, read) 
  2. __declspec(thread) int var = 0xDEADBEEF; 
  3. VOID NTAnopPI TlsCallback(PVOID DllHandle, DWORD Reason, VOID Reserved) 
  4.     var = 0xB15BADB0; // Required for TLS Callback call 
  5.     if (IsDebuggerPresent()) 
  6.     { 
  7.         MessageBoxA(NULL, "Stop debugging program!", "Error", MB_OK | MB_ICONERROR); 
  8.         TerminateProcess(GetCurrentProcess(), 0xBABEFACE); 
  9.     } 
  10. __declspec(allocate(".CRT$XLY"))PIMAGE_TLS_CALLBACK g_tlsCallback = TlsCallback; 

NtGlobalFlag

在Windows NT中,存在一组标识,它们存储在全局变量NtGlobalFlag中。在系统启动时,NtGlobalFlag全局系统变量将使用系统注册表项中的值进行初始化:

 
 
 
  1. [HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession ManagerGlobalFlag] 

该变量值用于系统跟踪,调试和控制。虽然变量标识未记录,但SDK包括gflags实用程序,它允许对一个全局标识值进行编辑。 PEB结构还包括NtGlobalFlag字段,其位结构不对应于NtGlobalFlag全局系统变量。在调试期间,这些标识在NtGlobalFlag字段中的设置如下:

 
 
 
  1. FLG_HEAP_ENABLE_TAIL_CHECK (0x10) 
  2. FLG_HEAP_ENABLE_FREE_CHECK (0x20) 
  3. FLG_HEAP_VALIDATE_PARAMETERS (0x40) 

要检查进程是否使用了调试器启动,你应该检查PEB结构的NtGlobalFlag字段的值。在x32和x64系统中,该字段位于PEB结构的开始处的0x068和0x0bc偏移处。

 
 
 
  1. 0:000> dt _PEB NtGlobalFlag @$peb  
  2. ntdll!_PEB 
  3.    +0x068 NtGlobalFlag : 0x70 

Windows X64里的进程如下:

 
 
 
  1. 0:000> dt _PEB NtGlobalFlag @$peb 
  2. ntdll!_PEB 
  3.    +0x0bc NtGlobalFlag : 0x70 

以下代码片段就是基于NtGlobalFlag标识检查的反调试保护:

 
 
 
  1. #define FLG_HEAP_ENABLE_TAIL_CHECK   0x10 
  2. #define FLG_HEAP_ENABLE_FREE_CHECK   0x20 
  3. #define FLG_HEAP_VALIDATE_PARAMETERS 0x40 
  4. #define NT_GLOBAL_FLAG_DEBUGGED (FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS) 
  5. void CheckNtGlobalFlag() 
  6.     PVOID pPeb = GetPEB(); 
  7.     PVOID pPeb64 = GetPEB64(); 
  8.     DWORD offsetNtGlobalFlag = 0; 
  9. #ifdef _WIN64 
  10.     offsetNtGlobalFlag = 0xBC; 
  11. #else 
  12.     offsetNtGlobalFlag = 0x68; 
  13. #endif 
  14.     DWORD NtGlobalFlag = *(PDWORD)((PBYTE)pPeb + offsetNtGlobalFlag); 
  15.     if (NtGlobalFlag & NT_GLOBAL_FLAG_DEBUGGED) 
  16.     { 
  17.         std::cout << "Stop debugging program!" << std::endl; 
  18.         exit(-1); 
  19.     } 
  20.     if (pPeb64) 
  21.     { 
  22.         DWORD NtGlobalFlagWow64 = *(PDWORD)((PBYTE)pPeb64 + 0xBC); 
  23.         if (NtGlobalFlagWow64 & NT_GLOBAL_FLAG_DEBUGGED) 
  24.         { 
  25.             std::cout << "Stop debugging program!" << std::endl; 
  26.             exit(-1); 
  27.         } 
  28.     } 

如何避开NtGlobalFlag检查

在执行该检查之前,应该在通过反调试保护检查该值之前,将0调整为调试过程中PEB结构的NtGlobalFlag字段。

NtGlobalFlag和IMAGE_LOAD_CONFIG_DIRECTORY

可执行文件既包括IMAGE_LOAD_CONFIG_DIRECTORY结构,也包括系统加载程序的其他配置参数。不过在默认情况下,此结构不会内置到可执行文件中,需要使用补丁添加。此结构具有GlobalFlagsClear字段,对PEB结构中要重置的NtGlobalFlag字段进行了标识。如果最初没有对该结构或GlobalFlagsClear = 0创建可执行文件,那么在磁盘或内存中,该字段就具有非零值,隐藏的调试器就会正常运行。下面就是检查运行进程的内存和磁盘上的GlobalFlagsClear字段的代码,这是一种流行的反调试技术:

 
 
 
  1. PIMAGE_NT_HEADERS GetImageNtHeaders(PBYTE pImageBase) 
  2.     PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)pImageBase; 
  3.     return (PIMAGE_NT_HEADERS)(pImageBase + pImageDosHeader->e_lfanew); 
  4. PIMAGE_SECTION_HEADER FindRDataSection(PBYTE pImageBase) 
  5.     static const std::string rdata = ".rdata"; 
  6.     PIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders(pImageBase); 
  7.     PIMAGE_SECTION_HEADER pImageSectionHeader = IMAGE_FIRST_SECTION(pImageNtHeaders); 
  8.     int n = 0; 
  9.     for (; n < pImageNtHeaders->FileHeader.NumberOfSections; ++n) 
  10.     { 
  11.         if (rdata == (char*)pImageSectionHeader[n].Name) 
  12.         { 
  13.             break; 
  14.         } 
  15.     } 
  16.     return &pImageSectionHeader[n]; 
  17. void CheckGlobalFlagsClearInProcess() 
  18.     PBYTE pImageBase = (PBYTE)GetModuleHandle(NULL); 
  19.     PIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders(pImageBase); 
  20.     PIMAGE_LOAD_CONFIG_DIRECTORY pImageLoadConfigDirectory = (PIMAGE_LOAD_CONFIG_DIRECTORY)(pImageBase 
  21.         + pImageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress); 
  22.     if (pImageLoadConfigDirectory->GlobalFlagsClear != 0) 
  23.     { 
  24.         std::cout << "Stop debugging program!" << std::endl; 
  25.         exit(-1); 
  26.     } 
  27. void CheckGlobalFlagsClearInFile() 
  28.     HANDLE hExecutable = INVALID_HANDLE_VALUE; 
  29.     HANDLE hExecutableMapping = NULL; 
  30.     PBYTE pMappedImageBase = NULL; 
  31.     __try 
  32.     { 
  33.         PBYTE pImageBase = (PBYTE)GetModuleHandle(NULL); 
  34.         PIMAGE_SECTION_HEADER pImageSectionHeader = FindRDataSection(pImageBase); 
  35.         TCHAR pszExecutablePath[MAX_PATH]; 
  36.         DWORD dwPathLength = GetModuleFileName(NULL, pszExecutablePath, MAX_PATH); 
  37.         if (0 == dwPathLength) __leave; 
  38.         hExecutable = CreateFile(pszExecutablePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 
  39.         if (INVALID_HANDLE_VALUE == hExecutable) __leave; 
  40.         hExecutableMapping = CreateFileMapping(hExecutable, NULL, PAGE_READONLY, 0, 0, NULL); 
  41.         if (NULL == hExecutableMapping) __leave; 
  42.         pMappedImageBase = (PBYTE)MapViewOfFile(hExecutableMapping, FILE_MAP_READ, 0, 0, 
  43.             pImageSectionHeader->PointerToRawData + pImageSectionHeader->SizeOfRawData); 
  44.         if (NULL == pMappedImageBase) __leave; 
  45.         PIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders(pMappedImageBase); 
  46.         PIMAGE_LOAD_CONFIG_DIRECTORY pImageLoadConfigDirectory = (PIMAGE_LOAD_CONFIG_DIRECTORY)(pMappedImageBase  
  47.             + (pImageSectionHeader->PointerToRawData 
  48.                 + (pImageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress - pImageSectionHeader->VirtualAddress))); 
  49.         if (pImageLoadConfigDirectory->GlobalFlagsClear != 0) 
  50.         { 
  51.             std::cout << "Stop debugging program!" << std::endl; 
  52.             exit(-1); 
  53.         } 
  54.     } 
  55.     __finally 
  56.     { 
  57.         if (NULL != pMappedImageBase) 
  58.             UnmapViewOfFile(pMappedImageBase); 
  59.         if (NULL != hExecutableMapping) 
  60.             CloseHandle(hExecutableMapping); 
  61.         if (INVALID_HANDLE_VALUE != hExecutable) 
  62.             CloseHandle(hExecutable); 
  63.     }  

在此代码中,CheckGlobalFlagsClearInProcess函数会通过加载当前运行的进程地址查找PIMAGE_LOAD_CONFIG_DIRECTORY结构,并检查GlobalFlagsClear字段值。如果不是0,那么该进程可能被调试。 CheckGlobalFlagsClearInFile函数也会执行相同的检查,但仅仅针对的是磁盘上的可执行文件。

HeapFlag和ForceFlags

PEB结构包含指向进程堆的指针— _HEAPP结构:

 
 
 
  1. 0:000> dt _PEB ProcessHeap @$peb 
  2. ntdll!_PEB 
  3.    +0x018 ProcessHeap : 0x00440000 Void0:000> dt _HEAP Flags ForceFlags 00440000 ntdll!_HEAP 
  4.    +0x040 Flags      : 0x40000062 
  5.    +0x044 ForceFlags : 0x40000060 

Windows X64里的进程如下:

 
 
 
  1. 0:000> dt _PEB ProcessHeap @$peb 
  2. ntdll!_PEB 
  3.    +0x030 ProcessHeap : 0x0000009d`94b60000 Void 
  4. 0:000> dt _HEAP Flags ForceFlags 0000009d`94b60000 
  5. ntdll!_HEAP 
  6.    +0x070 Flags      : 0x40000062 
  7.    +0x074 ForceFlags : 0x40000060 

如果正在调试进程,则两个字段Flags和ForceFlags都具有特定的调试值:

1.如果Flags字段没有设置HEAP_GROWABLE(0x00000002)标识,则正在调试进程。

2.如果ForceFlags!= 0,则正在调试进程。

不过要注意的是,_HEAP结构并未记录,并且Flags和ForceFlags字段的偏移值可能因操作系统版本而异。以下代码就是基于HeapFlag检查的反调试保护:

 
 
 
  1. int GetHeapFlagsOffset(bool x64) 
  2.     return x64 ? 
  3.         IsVistaOrHigher() ? 0x70 : 0x14: //x64 offsets 
  4.         IsVistaOrHigher() ? 0x40 : 0x0C; //x86 offsets 
  5. int GetHeapForceFlagsOffset(bool x64) 
  6.     return x64 ? 
  7.         IsVistaOrHigher() ? 0x74 : 0x18: //x64 offsets 
  8.         IsVistaOrHigher() ? 0x44 : 0x10; //x86 offsets 
  9. void CheckHeap() 
  10.     PVOID pPeb = GetPEB(); 
  11.     PVOID pPeb64 = GetPEB64(); 
  12.     PVOID heap = 0; 
  13.     DWORD offsetProcessHeap = 0; 
  14.     PDWORD heapFlagsPtr = 0, heapForceFlagsPtr = 0; 
  15.     BOOL x64 = FALSE; 
  16. #ifdef _WIN64 
  17.     x64 = TRUE; 
  18.     offsetProcessHeap = 0x30; 
  19. #else 
  20.     offsetProcessHeap = 0x18; 
  21. #endif 
  22.     heap = (PVOID)*(PDWORD_PTR)((PBYTE)pPeb + offsetProcessHeap); 
  23.     heapFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapFlagsOffset(x64)); 
  24.     heapForceFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapForceFlagsOffset(x64)); 
  25.     if (*heapFlagsPtr & ~HEAP_GROWABLE || *heapForceFlagsPtr != 0) 
  26.     { 
  27.         std::cout << "Stop debugging program!" << std::endl; 
  28.         exit(-1); 
  29.     } 
  30.     if (pPeb64) 
  31.     { 
  32.         heap = (PVOID)*(PDWORD_PTR)((PBYTE)pPeb64 + 0x30); 
  33.         heapFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapFlagsOffset(true)); 
  34.         heapForceFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapForceFlagsOffset(true)); 
  35.         if (*heapFlagsPtr & ~HEAP_GROWABLE || *heapForceFlagsPtr != 0) 
  36.         { 
  37.             std::cout << "Stop debugging program!" << std::endl; 
  38.             exit(-1); 
  39.         } 
  40.     } 

如何避开HeapFlag和ForceFlags检查

为了避开基于HeapFlag检查的反调试保护,应该为Flags字段设置HEAP_GROWABLE标识,并将ForceFlags的值设置为0.。但要注意的是,字段值的重新定义应该在HeapFlag检查之前执行。

陷阱标识检查

Trap Flag(陷阱标识)位于EFLAGS寄存器内,如果TF设置为1,CPU将在每个指令执行后产生INT 01h或单步异常(single-step exception)。以下就是基于TF设置和异常调用检查的反调试:

 
 
 
  1. BOOL isDebugged = TRUE; 
  2. __try 
  3.     __asm 
  4.     { 
  5.         pushfd 
  6.         or dword ptr[esp], 0x100 // set the Trap Flag  
  7.         popfd                    // Load the value into EFLAGS register 
  8.         nop 
  9.     } 
  10. __except (EXCEPTION_EXECUTE_HANDLER) 
  11.     // If an exception has been raised – debugger is not present 
  12.     isDebugged = FALSE; 
  13. if (isDebugged) 
  14.     std::cout << "Stop debugging program!" << std::endl; 
  15.     exit(-1); 

这里TF有意设置为生成异常。如果正在调试进程,则异常将被调试器捕获。

如何避开陷阱标识检查

为了在调试过程中避开TF标识检查,应该将pushfd指令传递给单步异常,但要跳过它,将断点置后,继续执行程序。断点后,跟踪可以继续。

CheckRemoteDebuggerPresent和NtQueryInformationProcess

与IsDebuggerPresent函数不同,CheckRemoteDebuggerPresent会检查一个进程是否被另一个同步进程调试。下图就是一个基于CheckRemoteDebuggerPresent的反调试技术:

 
 
 
  1. int main(int argc, char *argv[]) 
  2.     BOOL isDebuggerPresent = FALSE; 
  3.     if (CheckRemoteDebuggerPresent(GetCurrentProcess(), &isDebuggerPresent )) 
  4.     { 
  5.         if (isDebuggerPresent ) 
  6.         { 
  7.             std::cout << "Stop debugging program!" << std::endl; 
  8.             exit(-1); 
  9.         } 
  10.     } 
  11.     return 0; 

在CheckRemoteDebuggerPresent的内部,调用NtQueryInformationProcess函数:

 
 
 
  1. 0:000> uf kernelbase!CheckRemotedebuggerPresent 
  2. KERNELBASE!CheckRemoteDebuggerPresent: 
  3. ... 
  4. 75207a24 6a00            push    0 
  5. 75207a26 6a04            push    4 
  6. 75207a28 8d45fc          lea     eax,[ebp-4] 
  7. 75207a2b 50              push    eax 
  8. 75207a2c 6a07            push    7 
  9. 75207a2e ff7508          push    dword ptr [ebp+8] 
  10. 75207a31 ff151c602775    call    dword ptr [KERNELBASE!_imp__NtQueryInformationProcess (7527601c)] 
  11. 75207a37 85c0            test    eax,eax 
  12. 75207a39 0f88607e0100    js      KERNELBASE!CheckRemoteDebuggerPresent+0x2b (7521f89f) 
  13. ... 

如果我们来看看NtQueryInformationProcess文档,那么这个Assembler列表将向我们展示CheckRemoteDebuggerPresent函数获取DebugPort值,因为ProcessInformationClass参数值(第二个)为7,以下反调试代码就是基于调用NtQueryInformationProcess:

 
 
 
  1. typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)( 
  2.     _In_      HANDLE           ProcessHandle, 
  3.     _In_      UINT             ProcessInformationClass, 
  4.     _Out_     PVOID            ProcessInformation, 
  5.     _In_      ULONG            ProcessInformationLength, 
  6.     _Out_opt_ PULONG           ReturnLength 
  7.     ); 
  8. const UINT ProcessDebugPort = 7; 
  9. int main(int argc, char *argv[]) 
  10.     pfnNtQueryInformationProcess NtQueryInformationProcess = NULL; 
  11.     NTSTATUS status; 
  12.     DWORD isDebuggerPresent = 0; 
  13.     HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll")); 
  14.      
  15.     if (NULL != hNtDll) 
  16.     { 
  17.         NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess"); 
  18.         if (NULL != NtQueryInformationProcess) 
  19.         { 
  20.             status = NtQueryInformationProcess( 
  21.                 GetCurrentProcess(), 
  22.                 ProcessDebugPort, 
  23.                 &isDebuggerPresent, 
  24.                 sizeof(DWORD), 
  25.                 NULL); 
  26.             if (status == 0x00000000 && isDebuggerPresent != 0) 
  27.             { 
  28.                 std::cout << "Stop debugging program!" << std::endl; 
  29.                 exit(-1); 
  30.             } 
  31.         } 
  32.     } 
  33.     return 0; 

如何避开CheckRemoteDebuggerPresent和NtQueryInformationProcess

应该替换NtQueryInformationProcess函数返回的值。如果要使用mhook,就要先设置一个钩子,可以将DLL注入到调试过程中,并使用mhook在DLLMain中设置一个钩子。以下就是一个mhook用法的例子:

 
 
 
  1. #include  
  2. #include "mhook.h" 
  3. typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)( 
  4.     _In_      HANDLE           ProcessHandle, 
  5.     _In_      UINT             ProcessInformationClass, 
  6.     _Out_     PVOID            ProcessInformation, 
  7.     _In_      ULONG            ProcessInformationLength, 
  8.     _Out_opt_ PULONG           ReturnLength 
  9.     ); 
  10. const UINT ProcessDebugPort = 7; 
  11. pfnNtQueryInformationProcess g_origNtQueryInformationProcess = NULL; 
  12. NTSTATUS NTAPI HookNtQueryInformationProcess( 
  13.     _In_      HANDLE           ProcessHandle, 
  14.     _In_      UINT             ProcessInformationClass, 
  15.     _Out_     PVOID            ProcessInformation, 
  16.     _In_      ULONG            ProcessInformationLength, 
  17.     _Out_opt_ PULONG           ReturnLength 
  18.     ) 
  19.     NTSTATUS status = g_origNtQueryInformationProcess( 
  20.         ProcessHandle, 
  21.         ProcessInformationClass, 
  22.         ProcessInformation, 
  23.         ProcessInformationLength, 
  24.         ReturnLength); 
  25.     if (status == 0x00000000 && ProcessInformationClass == ProcessDebugPort) 
  26.     { 
  27.         *((PDWORD_PTR)ProcessInformation) = 0; 
  28.     } 
  29.     return status; 
  30. DWORD SetupHook(PVOID pvContext) 
  31.     HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll")); 
  32.     if (NULL != hNtDll) 
  33.     { 
  34.         g_origNtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess"); 
  35.         if (NULL != g_origNtQueryInformationProcess) 
  36.         { 
  37.             Mhook_SetHook((PVOID*)&g_origNtQueryInformationProcess, HookNtQueryInformationProcess); 
  38.         } 
  39.     } 
  40.     return 0; 
  41. BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpvReserved) 
  42.     switch (fdwReason) 
  43.     { 
  44.     case DLL_PROCESS_ATTACH: 
  45.         DisableThreadLibraryCalls(hInstDLL); 
  46.         CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)SetupHook, NULL, NULL, NULL); 
  47.         Sleep(20); 
  48.     case DLL_PROCESS_DETACH: 
  49.         if (NULL != g_origNtQueryInformationProcess) 
  50.         { 
  51.             Mhook_Unhook((PVOID*)&g_origNtQueryInformationProcess); 
  52.         } 
  53.         break; 
  54.     } 
  55.     return TRUE; 

基于NtQueryInformationProcess的其他反调试保护技术

可以从NtQueryInformationProcess函数提供的信息知道,还有更多的调试器检测技术:

1.ProcessDebugPort 0x07,已在上面讨论过。

2.ProcessDebugObjectHandle 0x1E

3.ProcessDebugFlags 0x1F

4.ProcessBasicInformation 0x00

ProcessDebugObjectHandle

从Windows XP开始,研究人员就为调试过程创建了调试对象。以下就是检查当前进程调试对象的案例:

 
 
 
  1. status = NtQueryInformationProcess( 
  2.             GetCurrentProcess(), 
  3.             ProcessDebugObjectHandle, 
  4.             &hProcessDebugObject, 
  5.             sizeof(HANDLE), 
  6.             NULL); 
  7. if (0x00000000 == status && NULL != hProcessDebugObject) 
  8.     std::cout << "Stop debugging program!" << std::endl; 
  9.     exit(-1); 

如果有调试对象,则正在调试该进程。

ProcessDebugFlags

当检查该标识时,它会返回到EPROCESS内核结构的NoDebugInherit位的反转值。如果NtQueryInformationProcess函数的返回值为0,则正在调试进程。以下就是一个这样的反调试检查的例子:

 
 
 
  1. status = NtQueryInformationProcess( 
  2.     GetCurrentProcess(), 
  3.     ProcessDebugObjectHandle, 
  4.     &debugFlags, 
  5.     sizeof(ULONG), 
  6.     NULL); 
  7. if (0x00000000 == status && NULL != debugFlags) 
  8.     std::cout << "Stop debugging program!" << std::endl; 
  9.     exit(-1); 
  10. }    

ProcessBasicInformation

当使用ProcessBasicInformation标识调用NtQueryInformationProcess函数时,会返回PROCESS_BASIC_INFORMATION结构:

 
 
 
  1. typedef struct _PROCESS_BASIC_INFORMATION { 
  2.     NTSTATUS ExitStatus; 
  3.     PVOID PebBaseAddress; 
  4.     ULONG_PTR AffinityMask; 
  5.     KPRIORITY BasePriority; 
  6.     HANDLE UniqueProcessId; 
  7.     HANDLE InheritedFromUniqueProcessId; 
  8. } PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION; 

该结构中最有趣的是InheritedFromUniqueProcessId字段。在这里,我们需要获取父进程的名称并将其与流行调试器的名称进行比较,以下是这种反调试检查的列表:

 
 
 
  1. std::wstring GetProcessNameById(DWORD pid) 
  2.     HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 
  3.     if (hProcessSnap == INVALID_HANDLE_VALUE) 
  4.     { 
  5.         return 0; 
  6.     } 
  7.     PROCESSENTRY32 pe32; 
  8.     pe32.dwSize = sizeof(PROCESSENTRY32); 
  9.     std::wstring processName = L""; 
  10.     if (!Process32First(hProcessSnap, &pe32)) 
  11.     { 
  12.         CloseHandle(hProcessSnap); 
  13.         return processName; 
  14.     } 
  15.     do 
  16.     { 
  17.         if (pe32.th32ProcessID == pid) 
  18.         { 
  19.             processName = pe32.szExeFile; 
  20.             break; 
  21.         } 
  22.     } while (Process32Next(hProcessSnap, &pe32)); 
  23.      
  24.     CloseHandle(hProcessSnap); 
  25.     return processName; 
  26. status = NtQueryInformationProcess( 
  27.     GetCurrentProcess(), 
  28.     ProcessBasicInformation, 
  29.     &processBasicInformation, 
  30.     sizeof(PROCESS_BASIC_INFORMATION), 
  31.     NULL); 
  32. std::wstring parentProcessName = GetProcessNameById((DWORD)processBasicInformation.InheritedFromUniqueProcessId); 
  33. if (L"devenv.exe" == parentProcessName) 
  34.     std::cout << "Stop debugging program!" << std::endl; 
  35.     exit(-1); 

如何避开NtQueryInformationProcess检查

避开是非常简单的, NtQueryInformationProcess函数返回的值应该更改为那些不指示调试器存在的值:

1.将ProcessDebugObjectHandle设置为0

2.将ProcessDebugFlags设置为1

3.对于ProcessBasicInformation,将InheritedFromUniqueProcessId值更改为另一个进程ID,例如, Explorer.exe的

断点

断点,调试器的功能之一,可以让程序中断在需要的地方,从而方便其分析。两种类型的断点:

1.软件断点

2.硬件断点

在没有断点的情况下很难进行逆向工程,所以目前流行的反逆向工程策略都是基于检测断点,然后提供一系列相应的反调试方法。

软件断点

在IA-32架构中,有一个特定的指令 – int 3h,带有0xCC操作码,用于调用调试句柄。当CPU执行该指令时,会产生中断并将控制传输到调试器。为了达到控制的目的,调试器必须将int 3h指令注入到代码中。要检测断点,我们可以计算函数的校验和。

 
 
 
  1. DWORD CalcFuncCrc(PUCHAR funcBegin, PUCHAR funcEnd) 
  2.     DWORD crc = 0; 
  3.     for (; funcBegin < funcEnd; ++funcBegin) 
  4.     { 
  5.         crc += *funcBegin; 
  6.     } 
  7.     return crc; 
  8. #pragma auto_inline(off) 
  9. VOID DebuggeeFunction() 
  10.     int calc = 0; 
  11.     calc += 2; 
  12.     calc <<= 8; 
  13.     calc -= 3; 
  14. VOID DebuggeeFunctionEnd() 
  15. }; 
  16. #pragma auto_inline(on) 
  17. DWORD g_origCrc = 0x2bd0; 
  18. int main() 
  19.     DWORD crc = CalcFuncCrc((PUCHAR)DebuggeeFunction, (PUCHAR)DebuggeeFunctionEnd); 
  20.     if (g_origCrc != crc)  名称栏目:26种对付反调试的方法
    网站URL:http://www.shufengxianlan.com/qtweb/news15/152315.html

    网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

    广告

    声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联