前言
常用反調試(Anti-Debug)檢測思路:
檢測PEB結構特定標志位,例如:"BeingDebugged"。
使用系統API,例如:"IsDebuggerPresent"等。
檢測指定調試器特征,例如:檢測進程,窗口標題等。
索引
// PEB_檢測 BeingDebugged NtGlobalFlag // Heap_檢測 HeapFlags ForceFlags // API_檢測 IsDebuggerPresent CheckRemoteDebuggerPresent NtQueryInformationProcess NtQuerySystemInformation // 時間_檢測 rdtsc GetTickCount // 硬件斷點_檢測 GetThreadContext // 異常_檢測 SEH // 內存CRC_檢測 --- // 分離調試器 NtSetInformationThread
示例
示例中的檢測代碼,僅用於效果演示,實際分析時檢測代碼並不會與示例代碼完全相同(作用或原理相同),另外某些數據結構在32/64系統中的偏移也不相同,需要讀者靈活應用后自行識別反調試!
PEB_檢測

// 如果被調試,BeingDebugged標志位 == 1 __asm { mov eax, fs:18h // TEB mov eax, [eax + 30h] // PEB movzx eax, [eax + 2] // PEB->BeingDebugged } // PEB偏移0x68,默認值==0,如果被調試系統會設置特定值(0x70) __asm { mov eax, fs:18h // TEB mov eax, [eax + 30h] // PEB movzx eax, [eax + 68h] // PEB->NtGlobalFlags and eax, 0x70 }
Heap_檢測

// 使用HeapFlags和ForceFlags檢測調試器並不可靠,但卻很常用 __asm { mov eax, fs:[30h] mov eax, [eax + 18h] // PEB.ProcessHeap mov eax, [eax + 0ch] // PEB.ProcessHeap.Flags cmp eax, 2 // 正常情況下,該值==2 } __asm { mov eax, fs:[30h] mov eax, [eax + 18h] // PEB.ProcessHeap mov eax, [eax + 10h] // PEB.ProcessHeap.ForceFlags test eax, eax // 正常情況下,該值 == 0 }
API_檢測

// 內部原理同PEB->BeingDebugged一樣 if (IsDebuggerPresent()) { // 調試 } else { // 未調試 }

// 檢測指定進程是否被調試,內部會調用NtQueryInformationProcess() BOOL result; CheckRemoteDebuggerPresent(NtCurrentProcess, &result); if (result) { // 調試 } else { // 未調試 }

PVOID pInfo; NTSTATUS status; ULONG returnlen; //如果進程被調試,其返回值應為0xffffffff status = NtQueryInformationProcess(NtCurrentProcess, ProcessDebugPort, &pInfo, sizeof(pInfo), &returnlen); if (NT_SUCCESS(status)) { if(pInfo != NULL) { //調試 } } // status = NtQueryInformationProcess(NtCurrentProcess, ProcessDebugObjectHandle, &pInfo, sizeof(pInfo), &returnlen); if (status == 0xC0000353) { //未調試 } else { //調試 } // 當調試器存在時,返回0 status = NtQueryInformationProcess(NtCurrentProcess, ProcessDebugFlags, &pInfo, sizeof(pInfo), &returnlen); if (NT_SUCCESS(status)) { if((ULONG_PTR)pInfo == 0)) { //調試 } }
時間_檢測

//利用rdtsc指令(操作碼0x0F31),它返回至系統重新啟動以來的時鍾數,並且將其作為一個64位的值存入EDX:EAX中 //惡意代碼運行兩次rdtsc指令,然后比較兩次讀取之間的差值 BOOL CheckDebug() { DWORD time1, time2; __asm { rdtsc mov time1, eax rdtsc mov time2, eax } if (time2 - time1 < 0xff) { //調試 } else { //未調試 } }

//該方法適合放在關鍵代碼處檢測,如果F9運行一般檢測不到 DWORD t1, t2; t1 = GetTickCount(); for (int i = 0; i < 0x100000; i++) { __asm{ nop; nop; nop; nop; } } t2 = GetTickCount(); if (t2 - t1 > 1000) { //調試 } else { //未調試 }
硬件斷點_檢測

//獲取線程上下文,檢測硬件斷點 CONTEXT ctx; ctx.ContextFlags = CONTEXT_ALL; GetThreadContext(NtCurrentThread,&ctx); if (ctx.Dr0 || ctx.Dr1 || ctx.Dr3 || ctx.Dr2) { //調試 } else { //未調試 }
異常_檢測

//======設置veh異常回調 LONG NTAPI VEHandle(PEXCEPTION_POINTERS ExceptionInfo) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == 0xC0000005) { //檢測硬件斷點 if (ExceptionInfo->ContextRecord->Dr0 || ExceptionInfo->ContextRecord->Dr1 || ExceptionInfo->ContextRecord->Dr2 || ExceptionInfo->ContextRecord->Dr3) { //調試 } else { //未調試 } ExceptionInfo->ContextRecord->Eip += 3; return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; } //======手動構造異常,在VEH中檢測硬件斷點調試 void SetVeh() { AddVectoredExceptionHandler(0, VEHandle); PUCHAR p = NULL; *p = NULL; RemoveVectoredExceptionHandler(VEHandle); }
內存CRC_檢測

//======內存檢測除了CRC也可以使用其它HASH函數,只要保證內存區段數據不被修改即可 void _CRCCheck() { DWORD start, size; //1.更早的 計算一遍頁面CRC PVOID imagebase; PIMAGE_DOS_HEADER pDosHead; PIMAGE_NT_HEADERS pNtHead; PIMAGE_SECTION_HEADER pSec; CRCPAGE_HASH ctx; //獲取DOS頭 imagebase = GetModuleHandle(NULL); pDosHead = (PIMAGE_DOS_HEADER)imagebase; if (pDosHead->e_magic != IMAGE_DOS_SIGNATURE) { return; } //獲取NT頭 pNtHead = (PIMAGE_NT_HEADERS)((ULONG_PTR)imagebase + pDosHead->e_lfanew); if (pNtHead->Signature != IMAGE_NT_SIGNATURE) { return; } //獲取區段頭 pSec = IMAGE_FIRST_SECTION(pNtHead); //遍歷所有區段,計算CRC g_crcPage.clear(); for (int i = 0; i < pNtHead->FileHeader.NumberOfSections; i++) { if (pSec->Characteristics & IMAGE_SCN_MEM_EXECUTE) { start = (ULONG_PTR)imagebase + pSec->VirtualAddress; size = (ULONG_PTR)pSec->Misc.VirtualSize; ctx.m_startAddr = (PVOID)start; ctx.m_size = size; ctx.m_hash = crc32((const void*)start, size); g_crcPage.push_back(ctx); } pSec++; } //計算完CRC后,在后面的某段代碼中再次計算,比較2次計算結果,如果不相同程序既有可能被調試或補丁 }
分離調試器

//該函數不會對正常運行的程序產生任何影響,但若運行的是調試器程序, //因為該函數隱藏了當前線程,調試器無法再收到該線程的調試事件,最終停止調試 void _ThreadHideFromDebugger() { NtSetInformationThread( NtCurrentThread, ThreadHideFromDebugger, //隱藏調試器線程 NULL, NULL); }
參考
網上有很多文章總結的都比較詳細,這里就不在做重復勞動了,具體可以參考以下文章。
//看雪 https://bbs.pediy.com/thread-70470.htm https://bbs.pediy.com/thread-225740.htm //比較全面,有反調試和虛擬機 https://github.com/LordNoteworthy/al-khaser //轉載的前面的文章,格式比較好,方便查看 https://www.cnblogs.com/huhu0013/archive/2011/07/05/2098358.html