新聞中心
目前主要有3種分析軟件的方法:

10年積累的網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站制作后付款的網(wǎng)站建設(shè)流程,更有阿合奇免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
1.數(shù)據(jù)交換分析,研究人員使用數(shù)據(jù)包嗅探工具來分析網(wǎng)絡(luò)數(shù)據(jù)交換。
2.對(duì)軟件的二進(jìn)制代碼進(jìn)行反匯編,然后以匯編語言列出。
3.字節(jié)碼解碼或二進(jìn)制解碼,然后以高級(jí)編程語言重新創(chuàng)建源代碼。
本文針對(duì)的是Windows操作系統(tǒng)中常用的防破解及防逆向工程保護(hù)技術(shù),即反調(diào)試方法,各種防逆向工程技術(shù)的主要目標(biāo)是盡可能多的使逆變工具盡可能失效。
本文的對(duì)付反調(diào)試方法,總共涉及26種:
1. IsDebuggerPresent
2. PEB(進(jìn)程環(huán)境塊)
3.如何避開IsDebuggerPresent的檢查
4.TLS回調(diào)
5.NtGlobalFlag
6.如何避開NtGlobalFlag檢查
7.NtGlobalFlag和IMAGE_LOAD_CONFIG_DIRECTORY
8.HeapFlag和ForceFlags
9.如何避開HeapFlag和ForceFlags
10.陷阱標(biāo)識(shí)檢查
11.如何避開陷阱標(biāo)識(shí)檢查
12.CheckRemoteDebuggerPresent和NtQueryInformationProcess
13.如何避開CheckRemoteDebuggerPresent和NtQueryInformationProcess
14.基于NtQueryInformationProcess的其他反調(diào)試保護(hù)技術(shù)
15.如何避開NtQueryInformationProcess檢查
16.軟件和硬件的斷點(diǎn)反應(yīng)
17.SEH(結(jié)構(gòu)化異常處理)
18.如何避開SHE檢查
19.VEH(向量化異常處理)
20.如何避開硬件斷點(diǎn)檢查和VEH
21.NtSetInformationThread ,在調(diào)試工具中隱藏線程
22.如何避開從調(diào)試工具中隱藏線程
23.NtCreateThreadEx
24. 如何避開NtCreateThreadEx
25.處理跟蹤
26.堆棧段操作
建議你在閱讀本文時(shí),先具備一定的Assembler知識(shí),一些Windbg操作經(jīng)驗(yàn)以及使用API函數(shù)開發(fā)Windows的經(jīng)驗(yàn)。
IsDebuggerPresent
也許最簡單的方法是調(diào)用IsDebuggerPresent函數(shù),用此函數(shù)檢測用戶模式的調(diào)試器是否正在調(diào)試調(diào)用進(jìn)程。下面的代碼就是一個(gè)基本的保護(hù)案例:
- int main()
- {
- if (IsDebuggerPresent())
- {
- std::cout << "Stop debugging program!" << std::endl;
- exit(-1);
- }
- return 0;
- }
如果我們來看看IsDebuggerPresent函數(shù),我們會(huì)發(fā)現(xiàn)這樣的代碼:
- 0:000< u kernelbase!IsDebuggerPresent L3
- KERNELBASE!IsDebuggerPresent:
- 751ca8d0 64a130000000 mov eax,dword ptr fs:[00000030h]
- 751ca8d6 0fb64002 movzx eax,byte ptr [eax+2]
- 751ca8da c3 ret
Windows X64里的進(jìn)程如下:
- 0:000< u kernelbase!IsDebuggerPresent L3
- KERNELBASE!IsDebuggerPresent:
- 00007ffc`ab6c1aa0 65488b042560000000 mov rax,qword ptr gs:[60h]
- 00007ffc`ab6c1aa9 0fb64002 movzx eax,byte ptr [rax+2]
- 00007ffc`ab6c1aad c3 ret
在FS寄存器的偏移量30h處存在PEB(進(jìn)程環(huán)境塊),而在X64上,PEB(進(jìn)程環(huán)境塊)存在于GS段寄存器的偏移量60h處。在PEB中的2個(gè)偏移量處,我們將找到BeingDebugged字段:
- 0:000< dt _PEB
- ntdll!_PEB
- +0x000 InheritedAddressSpace : UChar
- +0x001 ReadImageFileExecOptions : UChar
- +0x002 BeingDebugged : UChar
即IsDebuggerPresent函數(shù)讀取BeingDebugged字段的值。如果進(jìn)程被調(diào)試,值為1,否則為0。
PEB(進(jìn)程環(huán)境塊)
PEB是在操作系統(tǒng)內(nèi)使用的封閉結(jié)構(gòu)。在不同地運(yùn)行環(huán)境下,大家應(yīng)該以不同的方式獲取PEB結(jié)構(gòu)指針。如下所示,你可以在下圖中找到x32和x64系統(tǒng)的PEB指針:
- // Get PEB for WOW64 Process
- PVOID GetPEB64()
- {
- PVOID pPeb = 0;
- #ifndef _WIN64
- // 1. There are two copies of PEB - PEB64 and PEB32 in WOW64 process
- // 2. PEB64 follows after PEB32
- // 3. This is true for version less then Windows 8, else __readfsdword returns address of real PEB64
- if (IsWin8OrHigher())
- {
- BOOL isWow64 = FALSE;
- typedef BOOL(WINAPI *pfnIsWow64Process)(HANDLE hProcess, PBOOL isWow64);
- pfnIsWow64Process fnIsWow64Process = (pfnIsWow64Process)
- GetProcAddress(GetModuleHandleA("Kernel32.dll"), "IsWow64Process");
- if (fnIsWow64Process(GetCurrentProcess(), &isWow64))
- {
- if (isWow64)
- {
- pPeb = (PVOID)__readfsdword(0x0C * sizeof(PVOID));
- pPeb = (PVOID)((PBYTE)pPeb + 0x1000);
- }
- }
- }
- #endif
- return pPeb;
- }
檢查操作系統(tǒng)版本的功能代碼如下:
- WORD GetVersionWord()
- {
- OSVERSIONINFO verInfo = { sizeof(OSVERSIONINFO) };
- GetVersionEx(&verInfo);
- return MAKEWORD(verInfo.dwMinorVersion, verInfo.dwMajorVersion);
- }
- BOOL IsWin8OrHigher() { return GetVersionWord() >= _WIN32_WINNT_WIN8; }
- BOOL IsVistaOrHigher() { return GetVersionWord() >= _WIN32_WINNT_VISTA; }
如何避開IsDebuggerPresent檢查
為了做到這一點(diǎn),在執(zhí)行檢查代碼之前,需要將0置于BeingDebugged。例如,可以使用DLL注入:
- mov eax, dword ptr fs:[0x30]
- mov byte ptr ds:[eax+2], 0
Windows X64里的進(jìn)程如下:
- DWORD64 dwpeb = __readgsqword(0x60);
- *((PBYTE)(dwpeb + 2)) = 0;
TLS回調(diào)
其實(shí),在主函數(shù)中檢查調(diào)試器的存在不是最好的方法,因?yàn)門LS回調(diào)處于反匯編列表時(shí)反向工具的第一個(gè)位置。它實(shí)施的檢查可以由nop指令擦除,從而解除保護(hù)。如果使用CRT庫,則在將控制權(quán)轉(zhuǎn)移到主函數(shù)之前,主線程就已經(jīng)有一個(gè)調(diào)用堆棧了。執(zhí)行調(diào)試器存在檢查的一個(gè)方法便是TLS回調(diào)。如下圖所示,在可執(zhí)行模塊入口調(diào)用之前就已經(jīng)調(diào)用回調(diào)函數(shù)。
- #pragma section(".CRT$XLY", long, read)
- __declspec(thread) int var = 0xDEADBEEF;
- VOID NTAnopPI TlsCallback(PVOID DllHandle, DWORD Reason, VOID Reserved)
- {
- var = 0xB15BADB0; // Required for TLS Callback call
- if (IsDebuggerPresent())
- {
- MessageBoxA(NULL, "Stop debugging program!", "Error", MB_OK | MB_ICONERROR);
- TerminateProcess(GetCurrentProcess(), 0xBABEFACE);
- }
- }
- __declspec(allocate(".CRT$XLY"))PIMAGE_TLS_CALLBACK g_tlsCallback = TlsCallback;
NtGlobalFlag
在Windows NT中,存在一組標(biāo)識(shí),它們存儲(chǔ)在全局變量NtGlobalFlag中。在系統(tǒng)啟動(dòng)時(shí),NtGlobalFlag全局系統(tǒng)變量將使用系統(tǒng)注冊(cè)表項(xiàng)中的值進(jìn)行初始化:
- [HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession ManagerGlobalFlag]
該變量值用于系統(tǒng)跟蹤,調(diào)試和控制。雖然變量標(biāo)識(shí)未記錄,但SDK包括gflags實(shí)用程序,它允許對(duì)一個(gè)全局標(biāo)識(shí)值進(jìn)行編輯。 PEB結(jié)構(gòu)還包括NtGlobalFlag字段,其位結(jié)構(gòu)不對(duì)應(yīng)于NtGlobalFlag全局系統(tǒng)變量。在調(diào)試期間,這些標(biāo)識(shí)在NtGlobalFlag字段中的設(shè)置如下:
- FLG_HEAP_ENABLE_TAIL_CHECK (0x10)
- FLG_HEAP_ENABLE_FREE_CHECK (0x20)
- FLG_HEAP_VALIDATE_PARAMETERS (0x40)
要檢查進(jìn)程是否使用了調(diào)試器啟動(dòng),你應(yīng)該檢查PEB結(jié)構(gòu)的NtGlobalFlag字段的值。在x32和x64系統(tǒng)中,該字段位于PEB結(jié)構(gòu)的開始處的0x068和0x0bc偏移處。
- 0:000> dt _PEB NtGlobalFlag @$peb
- ntdll!_PEB
- +0x068 NtGlobalFlag : 0x70
Windows X64里的進(jìn)程如下:
- 0:000> dt _PEB NtGlobalFlag @$peb
- ntdll!_PEB
- +0x0bc NtGlobalFlag : 0x70
以下代碼片段就是基于NtGlobalFlag標(biāo)識(shí)檢查的反調(diào)試保護(hù):
- #define FLG_HEAP_ENABLE_TAIL_CHECK 0x10
- #define FLG_HEAP_ENABLE_FREE_CHECK 0x20
- #define FLG_HEAP_VALIDATE_PARAMETERS 0x40
- #define NT_GLOBAL_FLAG_DEBUGGED (FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS)
- void CheckNtGlobalFlag()
- {
- PVOID pPeb = GetPEB();
- PVOID pPeb64 = GetPEB64();
- DWORD offsetNtGlobalFlag = 0;
- #ifdef _WIN64
- offsetNtGlobalFlag = 0xBC;
- #else
- offsetNtGlobalFlag = 0x68;
- #endif
- DWORD NtGlobalFlag = *(PDWORD)((PBYTE)pPeb + offsetNtGlobalFlag);
- if (NtGlobalFlag & NT_GLOBAL_FLAG_DEBUGGED)
- {
- std::cout << "Stop debugging program!" << std::endl;
- exit(-1);
- }
- if (pPeb64)
- {
- DWORD NtGlobalFlagWow64 = *(PDWORD)((PBYTE)pPeb64 + 0xBC);
- if (NtGlobalFlagWow64 & NT_GLOBAL_FLAG_DEBUGGED)
- {
- std::cout << "Stop debugging program!" << std::endl;
- exit(-1);
- }
- }
- }
如何避開NtGlobalFlag檢查
在執(zhí)行該檢查之前,應(yīng)該在通過反調(diào)試保護(hù)檢查該值之前,將0調(diào)整為調(diào)試過程中PEB結(jié)構(gòu)的NtGlobalFlag字段。
NtGlobalFlag和IMAGE_LOAD_CONFIG_DIRECTORY
可執(zhí)行文件既包括IMAGE_LOAD_CONFIG_DIRECTORY結(jié)構(gòu),也包括系統(tǒng)加載程序的其他配置參數(shù)。不過在默認(rèn)情況下,此結(jié)構(gòu)不會(huì)內(nèi)置到可執(zhí)行文件中,需要使用補(bǔ)丁添加。此結(jié)構(gòu)具有GlobalFlagsClear字段,對(duì)PEB結(jié)構(gòu)中要重置的NtGlobalFlag字段進(jìn)行了標(biāo)識(shí)。如果最初沒有對(duì)該結(jié)構(gòu)或GlobalFlagsClear = 0創(chuàng)建可執(zhí)行文件,那么在磁盤或內(nèi)存中,該字段就具有非零值,隱藏的調(diào)試器就會(huì)正常運(yùn)行。下面就是檢查運(yùn)行進(jìn)程的內(nèi)存和磁盤上的GlobalFlagsClear字段的代碼,這是一種流行的反調(diào)試技術(shù):
- PIMAGE_NT_HEADERS GetImageNtHeaders(PBYTE pImageBase)
- {
- PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)pImageBase;
- return (PIMAGE_NT_HEADERS)(pImageBase + pImageDosHeader->e_lfanew);
- }
- PIMAGE_SECTION_HEADER FindRDataSection(PBYTE pImageBase)
- {
- static const std::string rdata = ".rdata";
- PIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders(pImageBase);
- PIMAGE_SECTION_HEADER pImageSectionHeader = IMAGE_FIRST_SECTION(pImageNtHeaders);
- int n = 0;
- for (; n < pImageNtHeaders->FileHeader.NumberOfSections; ++n)
- {
- if (rdata == (char*)pImageSectionHeader[n].Name)
- {
- break;
- }
- }
- return &pImageSectionHeader[n];
- }
- void CheckGlobalFlagsClearInProcess()
- {
- PBYTE pImageBase = (PBYTE)GetModuleHandle(NULL);
- PIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders(pImageBase);
- PIMAGE_LOAD_CONFIG_DIRECTORY pImageLoadConfigDirectory = (PIMAGE_LOAD_CONFIG_DIRECTORY)(pImageBase
- + pImageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress);
- if (pImageLoadConfigDirectory->GlobalFlagsClear != 0)
- {
- std::cout << "Stop debugging program!" << std::endl;
- exit(-1);
- }
- }
- void CheckGlobalFlagsClearInFile()
- {
- HANDLE hExecutable = INVALID_HANDLE_VALUE;
- HANDLE hExecutableMapping = NULL;
- PBYTE pMappedImageBase = NULL;
- __try
- {
- PBYTE pImageBase = (PBYTE)GetModuleHandle(NULL);
- PIMAGE_SECTION_HEADER pImageSectionHeader = FindRDataSection(pImageBase);
- TCHAR pszExecutablePath[MAX_PATH];
- DWORD dwPathLength = GetModuleFileName(NULL, pszExecutablePath, MAX_PATH);
- if (0 == dwPathLength) __leave;
- hExecutable = CreateFile(pszExecutablePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
- if (INVALID_HANDLE_VALUE == hExecutable) __leave;
- hExecutableMapping = CreateFileMapping(hExecutable, NULL, PAGE_READONLY, 0, 0, NULL);
- if (NULL == hExecutableMapping) __leave;
- pMappedImageBase = (PBYTE)MapViewOfFile(hExecutableMapping, FILE_MAP_READ, 0, 0,
- pImageSectionHeader->PointerToRawData + pImageSectionHeader->SizeOfRawData);
- if (NULL == pMappedImageBase) __leave;
- PIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders(pMappedImageBase);
- PIMAGE_LOAD_CONFIG_DIRECTORY pImageLoadConfigDirectory = (PIMAGE_LOAD_CONFIG_DIRECTORY)(pMappedImageBase
- + (pImageSectionHeader->PointerToRawData
- + (pImageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress - pImageSectionHeader->VirtualAddress)));
- if (pImageLoadConfigDirectory->GlobalFlagsClear != 0)
- {
- std::cout << "Stop debugging program!" << std::endl;
- exit(-1);
- }
- }
- __finally
- {
- if (NULL != pMappedImageBase)
- UnmapViewOfFile(pMappedImageBase);
- if (NULL != hExecutableMapping)
- CloseHandle(hExecutableMapping);
- if (INVALID_HANDLE_VALUE != hExecutable)
- CloseHandle(hExecutable);
- }
- }
在此代碼中,CheckGlobalFlagsClearInProcess函數(shù)會(huì)通過加載當(dāng)前運(yùn)行的進(jìn)程地址查找PIMAGE_LOAD_CONFIG_DIRECTORY結(jié)構(gòu),并檢查GlobalFlagsClear字段值。如果不是0,那么該進(jìn)程可能被調(diào)試。 CheckGlobalFlagsClearInFile函數(shù)也會(huì)執(zhí)行相同的檢查,但僅僅針對(duì)的是磁盤上的可執(zhí)行文件。
HeapFlag和ForceFlags
PEB結(jié)構(gòu)包含指向進(jìn)程堆的指針— _HEAPP結(jié)構(gòu):
- 0:000> dt _PEB ProcessHeap @$peb
- ntdll!_PEB
- +0x018 ProcessHeap : 0x00440000 Void0:000> dt _HEAP Flags ForceFlags 00440000 ntdll!_HEAP
- +0x040 Flags : 0x40000062
- +0x044 ForceFlags : 0x40000060
Windows X64里的進(jìn)程如下:
- 0:000> dt _PEB ProcessHeap @$peb
- ntdll!_PEB
- +0x030 ProcessHeap : 0x0000009d`94b60000 Void
- 0:000> dt _HEAP Flags ForceFlags 0000009d`94b60000
- ntdll!_HEAP
- +0x070 Flags : 0x40000062
- +0x074 ForceFlags : 0x40000060
如果正在調(diào)試進(jìn)程,則兩個(gè)字段Flags和ForceFlags都具有特定的調(diào)試值:
1.如果Flags字段沒有設(shè)置HEAP_GROWABLE(0x00000002)標(biāo)識(shí),則正在調(diào)試進(jìn)程。
2.如果ForceFlags!= 0,則正在調(diào)試進(jìn)程。
不過要注意的是,_HEAP結(jié)構(gòu)并未記錄,并且Flags和ForceFlags字段的偏移值可能因操作系統(tǒng)版本而異。以下代碼就是基于HeapFlag檢查的反調(diào)試保護(hù):
- int GetHeapFlagsOffset(bool x64)
- {
- return x64 ?
- IsVistaOrHigher() ? 0x70 : 0x14: //x64 offsets
- IsVistaOrHigher() ? 0x40 : 0x0C; //x86 offsets
- }
- int GetHeapForceFlagsOffset(bool x64)
- {
- return x64 ?
- IsVistaOrHigher() ? 0x74 : 0x18: //x64 offsets
- IsVistaOrHigher() ? 0x44 : 0x10; //x86 offsets
- }
- void CheckHeap()
- {
- PVOID pPeb = GetPEB();
- PVOID pPeb64 = GetPEB64();
- PVOID heap = 0;
- DWORD offsetProcessHeap = 0;
- PDWORD heapFlagsPtr = 0, heapForceFlagsPtr = 0;
- BOOL x64 = FALSE;
- #ifdef _WIN64
- x64 = TRUE;
- offsetProcessHeap = 0x30;
- #else
- offsetProcessHeap = 0x18;
- #endif
- heap = (PVOID)*(PDWORD_PTR)((PBYTE)pPeb + offsetProcessHeap);
- heapFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapFlagsOffset(x64));
- heapForceFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapForceFlagsOffset(x64));
- if (*heapFlagsPtr & ~HEAP_GROWABLE || *heapForceFlagsPtr != 0)
- {
- std::cout << "Stop debugging program!" << std::endl;
- exit(-1);
- }
- if (pPeb64)
- {
- heap = (PVOID)*(PDWORD_PTR)((PBYTE)pPeb64 + 0x30);
- heapFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapFlagsOffset(true));
- heapForceFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapForceFlagsOffset(true));
- if (*heapFlagsPtr & ~HEAP_GROWABLE || *heapForceFlagsPtr != 0)
- {
- std::cout << "Stop debugging program!" << std::endl;
- exit(-1);
- }
- }
- }
如何避開HeapFlag和ForceFlags檢查
為了避開基于HeapFlag檢查的反調(diào)試保護(hù),應(yīng)該為Flags字段設(shè)置HEAP_GROWABLE標(biāo)識(shí),并將ForceFlags的值設(shè)置為0.。但要注意的是,字段值的重新定義應(yīng)該在HeapFlag檢查之前執(zhí)行。
陷阱標(biāo)識(shí)檢查
Trap Flag(陷阱標(biāo)識(shí))位于EFLAGS寄存器內(nèi),如果TF設(shè)置為1,CPU將在每個(gè)指令執(zhí)行后產(chǎn)生INT 01h或單步異常(single-step exception)。以下就是基于TF設(shè)置和異常調(diào)用檢查的反調(diào)試:
- BOOL isDebugged = TRUE;
- __try
- {
- __asm
- {
- pushfd
- or dword ptr[esp], 0x100 // set the Trap Flag
- popfd // Load the value into EFLAGS register
- nop
- }
- }
- __except (EXCEPTION_EXECUTE_HANDLER)
- {
- // If an exception has been raised – debugger is not present
- isDebugged = FALSE;
- }
- if (isDebugged)
- {
- std::cout << "Stop debugging program!" << std::endl;
- exit(-1);
- }
這里TF有意設(shè)置為生成異常。如果正在調(diào)試進(jìn)程,則異常將被調(diào)試器捕獲。
如何避開陷阱標(biāo)識(shí)檢查
為了在調(diào)試過程中避開TF標(biāo)識(shí)檢查,應(yīng)該將pushfd指令傳遞給單步異常,但要跳過它,將斷點(diǎn)置后,繼續(xù)執(zhí)行程序。斷點(diǎn)后,跟蹤可以繼續(xù)。
CheckRemoteDebuggerPresent和NtQueryInformationProcess
與IsDebuggerPresent函數(shù)不同,CheckRemoteDebuggerPresent會(huì)檢查一個(gè)進(jìn)程是否被另一個(gè)同步進(jìn)程調(diào)試。下圖就是一個(gè)基于CheckRemoteDebuggerPresent的反調(diào)試技術(shù):
- int main(int argc, char *argv[])
- {
- BOOL isDebuggerPresent = FALSE;
- if (CheckRemoteDebuggerPresent(GetCurrentProcess(), &isDebuggerPresent ))
- {
- if (isDebuggerPresent )
- {
- std::cout << "Stop debugging program!" << std::endl;
- exit(-1);
- }
- }
- return 0;
- }
在CheckRemoteDebuggerPresent的內(nèi)部,調(diào)用NtQueryInformationProcess函數(shù):
- 0:000> uf kernelbase!CheckRemotedebuggerPresent
- KERNELBASE!CheckRemoteDebuggerPresent:
- ...
- 75207a24 6a00 push 0
- 75207a26 6a04 push 4
- 75207a28 8d45fc lea eax,[ebp-4]
- 75207a2b 50 push eax
- 75207a2c 6a07 push 7
- 75207a2e ff7508 push dword ptr [ebp+8]
- 75207a31 ff151c602775 call dword ptr [KERNELBASE!_imp__NtQueryInformationProcess (7527601c)]
- 75207a37 85c0 test eax,eax
- 75207a39 0f88607e0100 js KERNELBASE!CheckRemoteDebuggerPresent+0x2b (7521f89f)
- ...
如果我們來看看NtQueryInformationProcess文檔,那么這個(gè)Assembler列表將向我們展示CheckRemoteDebuggerPresent函數(shù)獲取DebugPort值,因?yàn)镻rocessInformationClass參數(shù)值(第二個(gè))為7,以下反調(diào)試代碼就是基于調(diào)用NtQueryInformationProcess:
- typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)(
- _In_ HANDLE ProcessHandle,
- _In_ UINT ProcessInformationClass,
- _Out_ PVOID ProcessInformation,
- _In_ ULONG ProcessInformationLength,
- _Out_opt_ PULONG ReturnLength
- );
- const UINT ProcessDebugPort = 7;
- int main(int argc, char *argv[])
- {
- pfnNtQueryInformationProcess NtQueryInformationProcess = NULL;
- NTSTATUS status;
- DWORD isDebuggerPresent = 0;
- HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll"));
- if (NULL != hNtDll)
- {
- NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess");
- if (NULL != NtQueryInformationProcess)
- {
- status = NtQueryInformationProcess(
- GetCurrentProcess(),
- ProcessDebugPort,
- &isDebuggerPresent,
- sizeof(DWORD),
- NULL);
- if (status == 0x00000000 && isDebuggerPresent != 0)
- {
- std::cout << "Stop debugging program!" << std::endl;
- exit(-1);
- }
- }
- }
- return 0;
- }
如何避開CheckRemoteDebuggerPresent和NtQueryInformationProcess
應(yīng)該替換NtQueryInformationProcess函數(shù)返回的值。如果要使用mhook,就要先設(shè)置一個(gè)鉤子,可以將DLL注入到調(diào)試過程中,并使用mhook在DLLMain中設(shè)置一個(gè)鉤子。以下就是一個(gè)mhook用法的例子:
- #include
- #include "mhook.h"
- typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)(
- _In_ HANDLE ProcessHandle,
- _In_ UINT ProcessInformationClass,
- _Out_ PVOID ProcessInformation,
- _In_ ULONG ProcessInformationLength,
- _Out_opt_ PULONG ReturnLength
- );
- const UINT ProcessDebugPort = 7;
- pfnNtQueryInformationProcess g_origNtQueryInformationProcess = NULL;
- NTSTATUS NTAPI HookNtQueryInformationProcess(
- _In_ HANDLE ProcessHandle,
- _In_ UINT ProcessInformationClass,
- _Out_ PVOID ProcessInformation,
- _In_ ULONG ProcessInformationLength,
- _Out_opt_ PULONG ReturnLength
- )
- {
- NTSTATUS status = g_origNtQueryInformationProcess(
- ProcessHandle,
- ProcessInformationClass,
- ProcessInformation,
- ProcessInformationLength,
- ReturnLength);
- if (status == 0x00000000 && ProcessInformationClass == ProcessDebugPort)
- {
- *((PDWORD_PTR)ProcessInformation) = 0;
- }
- return status;
- }
- DWORD SetupHook(PVOID pvContext)
- {
- HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll"));
- if (NULL != hNtDll)
- {
- g_origNtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess");
- if (NULL != g_origNtQueryInformationProcess)
- {
- Mhook_SetHook((PVOID*)&g_origNtQueryInformationProcess, HookNtQueryInformationProcess);
- }
- }
- return 0;
- }
- BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpvReserved)
- {
- switch (fdwReason)
- {
- case DLL_PROCESS_ATTACH:
- DisableThreadLibraryCalls(hInstDLL);
- CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)SetupHook, NULL, NULL, NULL);
- Sleep(20);
- case DLL_PROCESS_DETACH:
- if (NULL != g_origNtQueryInformationProcess)
- {
- Mhook_Unhook((PVOID*)&g_origNtQueryInformationProcess);
- }
- break;
- }
- return TRUE;
- }
基于NtQueryInformationProcess的其他反調(diào)試保護(hù)技術(shù)
可以從NtQueryInformationProcess函數(shù)提供的信息知道,還有更多的調(diào)試器檢測技術(shù):
1.ProcessDebugPort 0x07,已在上面討論過。
2.ProcessDebugObjectHandle 0x1E
3.ProcessDebugFlags 0x1F
4.ProcessBasicInformation 0x00
ProcessDebugObjectHandle
從Windows XP開始,研究人員就為調(diào)試過程創(chuàng)建了調(diào)試對(duì)象。以下就是檢查當(dāng)前進(jìn)程調(diào)試對(duì)象的案例:
- status = NtQueryInformationProcess(
- GetCurrentProcess(),
- ProcessDebugObjectHandle,
- &hProcessDebugObject,
- sizeof(HANDLE),
- NULL);
- if (0x00000000 == status && NULL != hProcessDebugObject)
- {
- std::cout << "Stop debugging program!" << std::endl;
- exit(-1);
- }
如果有調(diào)試對(duì)象,則正在調(diào)試該進(jìn)程。
ProcessDebugFlags
當(dāng)檢查該標(biāo)識(shí)時(shí),它會(huì)返回到EPROCESS內(nèi)核結(jié)構(gòu)的NoDebugInherit位的反轉(zhuǎn)值。如果NtQueryInformationProcess函數(shù)的返回值為0,則正在調(diào)試進(jìn)程。以下就是一個(gè)這樣的反調(diào)試檢查的例子:
- status = NtQueryInformationProcess(
- GetCurrentProcess(),
- ProcessDebugObjectHandle,
- &debugFlags,
- sizeof(ULONG),
- NULL);
- if (0x00000000 == status && NULL != debugFlags)
- {
- std::cout << "Stop debugging program!" << std::endl;
- exit(-1);
- }
ProcessBasicInformation
當(dāng)使用ProcessBasicInformation標(biāo)識(shí)調(diào)用NtQueryInformationProcess函數(shù)時(shí),會(huì)返回PROCESS_BASIC_INFORMATION結(jié)構(gòu):
- typedef struct _PROCESS_BASIC_INFORMATION {
- NTSTATUS ExitStatus;
- PVOID PebBaseAddress;
- ULONG_PTR AffinityMask;
- KPRIORITY BasePriority;
- HANDLE UniqueProcessId;
- HANDLE InheritedFromUniqueProcessId;
- } PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;
該結(jié)構(gòu)中最有趣的是InheritedFromUniqueProcessId字段。在這里,我們需要獲取父進(jìn)程的名稱并將其與流行調(diào)試器的名稱進(jìn)行比較,以下是這種反調(diào)試檢查的列表:
- std::wstring GetProcessNameById(DWORD pid)
- {
- HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
- if (hProcessSnap == INVALID_HANDLE_VALUE)
- {
- return 0;
- }
- PROCESSENTRY32 pe32;
- pe32.dwSize = sizeof(PROCESSENTRY32);
- std::wstring processName = L"";
- if (!Process32First(hProcessSnap, &pe32))
- {
- CloseHandle(hProcessSnap);
- return processName;
- }
- do
- {
- if (pe32.th32ProcessID == pid)
- {
- processName = pe32.szExeFile;
- break;
- }
- } while (Process32Next(hProcessSnap, &pe32));
- CloseHandle(hProcessSnap);
- return processName;
- }
- status = NtQueryInformationProcess(
- GetCurrentProcess(),
- ProcessBasicInformation,
- &processBasicInformation,
- sizeof(PROCESS_BASIC_INFORMATION),
- NULL);
- std::wstring parentProcessName = GetProcessNameById((DWORD)processBasicInformation.InheritedFromUniqueProcessId);
- if (L"devenv.exe" == parentProcessName)
- {
- std::cout << "Stop debugging program!" << std::endl;
- exit(-1);
- }
如何避開NtQueryInformationProcess檢查
避開是非常簡單的, NtQueryInformationProcess函數(shù)返回的值應(yīng)該更改為那些不指示調(diào)試器存在的值:
1.將ProcessDebugObjectHandle設(shè)置為0
2.將ProcessDebugFlags設(shè)置為1
3.對(duì)于ProcessBasicInformation,將InheritedFromUniqueProcessId值更改為另一個(gè)進(jìn)程ID,例如, Explorer.exe的
斷點(diǎn)
斷點(diǎn),調(diào)試器的功能之一,可以讓程序中斷在需要的地方,從而方便其分析。兩種類型的斷點(diǎn):
1.軟件斷點(diǎn)
2.硬件斷點(diǎn)
在沒有斷點(diǎn)的情況下很難進(jìn)行逆向工程,所以目前流行的反逆向工程策略都是基于檢測斷點(diǎn),然后提供一系列相應(yīng)的反調(diào)試方法。
軟件斷點(diǎn)
在IA-32架構(gòu)中,有一個(gè)特定的指令 – int 3h,帶有0xCC操作碼,用于調(diào)用調(diào)試句柄。當(dāng)CPU執(zhí)行該指令時(shí),會(huì)產(chǎn)生中斷并將控制傳輸?shù)秸{(diào)試器。為了達(dá)到控制的目的,調(diào)試器必須將int 3h指令注入到代碼中。要檢測斷點(diǎn),我們可以計(jì)算函數(shù)的校驗(yàn)和。
- DWORD CalcFuncCrc(PUCHAR funcBegin, PUCHAR funcEnd)
- {
- DWORD crc = 0;
- for (; funcBegin < funcEnd; ++funcBegin)
- {
- crc += *funcBegin;
- }
- return crc;
- }
- #pragma auto_inline(off)
- VOID DebuggeeFunction()
- {
- int calc = 0;
- calc += 2;
- calc <<= 8;
- calc -= 3;
- }
- VOID DebuggeeFunctionEnd()
- {
- };
- #pragma auto_inline(on)
- DWORD g_origCrc = 0x2bd0;
- int main()
- {
- DWORD crc = CalcFuncCrc((PUCHAR)DebuggeeFunction, (PUCHAR)DebuggeeFunctionEnd);
- if (g_origCrc != crc) 當(dāng)前名稱:26種對(duì)付反調(diào)試的方法
文章URL:http://www.dlmjj.cn/article/cdjgddc.html


咨詢
建站咨詢
