稍稍總結一下在Crack或Rervese中比較常見的一些反調試方法,實現起來也比較簡單,之后有寫的Demo源碼參考,沒有太大的難度。
①最簡單也是最基礎的,Windows提供的API接口:IsDebuggerPresent(),這API實際上就是訪問PEB的BeingDebugged標志來判斷是否處於調試狀態。
if (IsDebuggerPresent()) //API接口 { AfxMessageBox(L"檢測到調試器"); } else { AfxMessageBox(L"沒有檢測到調試器"); }
②自己用匯編實現實現IsDebuggerPresent,如何獲得PEB呢?在應用層,fs寄存器是指向當前線程的TEB結構的,而在內核層,fs寄存器指向PCR(Processor Control Region)的內存,其數據類型是KPCR。所以可以通過當前線程的TEB獲得PEB,再取出BeingDebugged的值。
mov eax, fs:18h // TEB Self指針 mov eax, [eax+30h] // PEB movzx eax, [eax+2] // PEB->BeingDebugged
_asm { push eax; //TEB mov eax, fs:[0x30]; // PEB movzx eax, byte ptr[eax + 2]; //BeingDebugged mov dword ptr[Value], eax; //取值 pop eax; } if (Value) //判斷 { AfxMessageBox(L"檢測到調試器"); } else { AfxMessageBox(L"沒有檢測到調試器"); }
講個題外話,也是關於FS寄存器的前兩天有人是問了我一個問題,說是怎么讓dll只被特定的進程加載,了解fs寄存器之后就很簡單了,在dll加載的DllMain()函數中對加載的當前進程進行判斷,看是否是自己的目標進程,如果不是,就拒絕被加載。下面是一段測試代碼,可以獲得當前進程的進程名。
#include "windows.h" //這段測試代碼可以獲得當前進程名 #include "stdio.h" int main(void) { LPSTR name; __asm{ mov eax,fs:[0x18] //得到Teb 當線程運行於用戶空間時段寄存器FS指向當前線程的TEB,FS:[0x18] 就是指在 //Self,其內容就是TEB的起點 mov eax,[eax+0x30] //Teb偏移0x30處指向Peb mov eax,[eax+0xc] //Peb偏移 0xc 處指向 進程加載的模塊的信息 _PEB_LDR_DATA結構 mov eax,[eax+0xc] mov eax,[eax+0x30] mov name,eax } wprintf(L"%s\n",name); return 0; }
③NtGlobalFlag 也是類似於BeingDebugged的一個標志,在Peb中取出。如果是調試狀態下,
NtGlobalFlag的值會是0x70,正常情況下不是。之前是利用fs寄存器獲得的Peb基地址,這里使用Native API 的方法。
HANDLE hProcess = NULL; DWORD ProcessId = 0; PROCESS_BASIC_INFORMATION Pbi; PFNZwQueryInformationProcess pfunc_ZwQueryInformationProcess = NULL; ProcessId = GetCurrentProcessId(); hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, ProcessId ); if (hProcess != NULL) { HMODULE hModule = LoadLibrary(L"ntdll.dll"); pfunc_ZwQueryInformationProcess = (PFNZwQueryInformationProcess)GetProcAddress(hModule, "ZwQueryInformationProcess"); NTSTATUS Status = pfunc_ZwQueryInformationProcess( hProcess, ProcessBasicInformation, &Pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL ); if (NT_SUCCESS(Status)) { DWORD ByteRead = 0; WORD NtGlobalFlag = 0; bool bIsDebug = false; ULONG PebBase = (ULONG)Pbi.PebBaseAddress; if (ReadProcessMemory(hProcess, (LPCVOID)(PebBase + 0x68), &NtGlobalFlag, 2, &ByteRead) && ByteRead == 2) { // PEB.NtGlobalFlag // +0x068 NtGlobalFlag : Uint4B if (NtGlobalFlag == 0x70) //判斷是否處於調試狀態 { bIsDebug = true; } } if (bIsDebug) { AfxMessageBox(L"檢測到調試器"); } else { AfxMessageBox(L"沒有檢測到調試器"); } } CloseHandle(hProcess);
④PEB.ProcessHeap.Flags 和ForceFlags
進程堆標志,系統創建進程時會將Flags置為0x02(HEAP_GROWABLE),將ForceFlags置為0。但是進程被調試時,這兩個標志通常被設置為0x50000062h和0x40000060h。下面是檢測代碼
HANDLE hProcess = NULL; DWORD ProcessId = 0; PROCESS_BASIC_INFORMATION Pbi; PFNZwQueryInformationProcess pfunc_pZwQueryInformationProcess = NULL; ULONG Flags = 0; ULONG ForceFlags = 0; switch (g_WinVersion) { case WINDOWS_VERSION_XP: Flags = 0xC; ForceFlags = 0x10; break; case WINDOWS_VERSION_2K3_SP1_SP2: Flags = 0xC; ForceFlags = 0x10; break; case WINDOWS_VERSION_7: Flags = 0x40; ForceFlags = 0x44; break; } ProcessId = GetCurrentProcessId(); hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, ProcessId ); if (hProcess != NULL) { HMODULE hModule = LoadLibrary(L"ntdll.dll"); pfunc_pZwQueryInformationProcess = (PFNZwQueryInformationProcess)GetProcAddress(hModule, "ZwQueryInformationProcess"); NTSTATUS Status = pfunc_pZwQueryInformationProcess( hProcess, ProcessBasicInformation, &Pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL ); if (NT_SUCCESS(Status)) { DWORD ByteRead = 0; ULONG ProcessHeap = 0; DWORD HeapFlags = 0; DWORD ForceFlagsValue = 1; bool bIsDebug = false; ULONG PebBase = (ULONG)Pbi.PebBaseAddress; if (ReadProcessMemory(hProcess, (LPCVOID)(PebBase + 0x18), &ProcessHeap, 4, &ByteRead)) { if (ReadProcessMemory(hProcess, (LPCVOID)(ProcessHeap + Flags), &HeapFlags, 4, &ByteRead)) { // PEB.ProcessHeap.Flags // nt!_PEB // +0x018 ProcessHeap : Ptr32 Void _HEAP // nt!_HEAP // WIN7 ProcessHeap 還有一個 Heap // +0x018 Heap : Ptr32 _HEAP // WIN7 // +0x040 Flags : Uint4B // +0x044 ForceFlags : Uint4B // WIN2K3 // +0x00C Flags : Uint4B // +0x010 ForceFlags : Uint4B // WINXP // +0x00C Flags : Uint4B // +0x010 ForceFlags : Uint4B if (HeapFlags != 2) //系統創建進程時會將其設置為HEAP_GROEABLE(0x2) { bIsDebug = true; } else { ReadProcessMemory(hProcess,(LPCVOID)(PebBase+ForceFlags),&ForceFlagsValue,4,&ByteRead); if (ForceFlagsValue != 0) { bIsDebug = true; } } } } if (bIsDebug) { AfxMessageBox(L"檢測到調試器"); } else { AfxMessageBox(L"沒有檢測到調試器"); } } CloseHandle(hProcess); }
⑤檢測父進程
一般雙擊運行的進程的父進程都是explorer.exe,但是如果進程被調試,父進程則是調試器進程,也就是說如果父進程不是explorer.exe,就可以認為有調試器存在。
DWORD ExplorerId = 0; PROCESSENTRY32 pe32 = {0}; CString str; BOOL bIsDebug = FALSE; DWORD ProcessId = GetCurrentProcessId(); // 獲取 Explorer 進程ID ::GetWindowThreadProcessId(::FindWindow(L"Progman", NULL), &ExplorerId); HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); if (hProcessSnap != INVALID_HANDLE_VALUE) { pe32.dwSize = sizeof(PROCESSENTRY32); if (Process32First(hProcessSnap, &pe32)) { do { if (ProcessId == pe32.th32ProcessID) { str.Format(L"進程ID:%d 父進程ID:%d Explorer進程ID:%d", ProcessId, pe32.th32ParentProcessID, ExplorerId); if (pe32.th32ParentProcessID != ExplorerId) { bIsDebug = TRUE; break; } } } while (Process32Next(hProcessSnap, &pe32)); } AfxMessageBox(str); } if (bIsDebug) { AfxMessageBox(L"檢測到調試器"); } else { AfxMessageBox(L"沒有檢測到調試器"); } CloseHandle (hProcessSnap);
⑥SeDebugPrivilege權限判斷
默認情況下進程是沒有SeDebugPrivilege權限的,但是當進程通過OD或者xdbg等調試器啟動時,由於調試器本身啟動了SeDebugPrivilege權限,當調試進程被加載時SeDebugPrivilege也就被繼承了。所以我們可以檢測進程的SeDebugPrivilege權限來間接判斷是否存在調試器,而對SeDebugPrivilege權限的判斷可以用能否打開csrss.exe進程來判斷
HANDLE hProcess = NULL; PROCESSENTRY32 Pe32 = {0}; HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hProcessSnap == INVALID_HANDLE_VALUE) { return; } Pe32.dwSize = sizeof(PROCESSENTRY32); if (Process32First(hProcessSnap, &Pe32)) { do { if (_wcsicmp(L"csrss.exe", Pe32.szExeFile) == 0) { HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, Pe32.th32ProcessID ); if (hProcess) { AfxMessageBox(L"檢測到調試器"); } else { AfxMessageBox(L"沒有檢測到調試器"); } CloseHandle(hProcess); } } while (Process32Next(hProcessSnap, &Pe32)); } CloseHandle(hProcessSnap);
⑦檢測硬件斷點,其實也不能算是完完全全對調試器的檢測,只能說是檢測硬件斷點,通過設置SHE處理例程,然后自己手動觸發異常來檢測硬件斷點。
#pragma data_seg(".Hardware") BOOL Hardwarei = FALSE; DWORD addr = 0; #pragma data_seg() #pragma comment(linker, "/section:.Hardware,RWS") LONG WINAPI HardwareExceptionFilter( PEXCEPTION_POINTERS ExceptionInfo ) { if (ExceptionInfo->ContextRecord->Dr0 != 0 || ExceptionInfo->ContextRecord->Dr1 != 0 || ExceptionInfo->ContextRecord->Dr2 != 0 || ExceptionInfo->ContextRecord->Dr3 != 0) { Hardwarei = TRUE; ExceptionInfo->ContextRecord->Dr0 = 0; ExceptionInfo->ContextRecord->Dr1 = 0; ExceptionInfo->ContextRecord->Dr2 = 0; ExceptionInfo->ContextRecord->Dr3 = 0; } // 設置新的eip 讓程序調轉到safe執行 ExceptionInfo->ContextRecord->Eip = addr; return EXCEPTION_CONTINUE_EXECUTION; } BOOL IsHardware() { LPTOP_LEVEL_EXCEPTION_FILTER lpsetun; lpsetun = ::SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)HardwareExceptionFilter); _asm { mov addr, offset safe; int 3; } safe: ::SetUnhandledExceptionFilter(lpsetun); return Hardwarei; } void CAntiDebugDlg::OnBnClickedButton12() { char *p = "aaaaaaaaaaaaaaddddddddddddddbbbbbbbbbbbb"; if (IsHardware()) { AfxMessageBox(L"檢測到硬件執行斷點"); } else { AfxMessageBox(L"沒有檢測到硬件執行斷點"); } }
⑧上面是通過SHE來檢測硬件斷點,當然還可以通過VEH來檢測,思路是一樣的,差別就是SHE與VEH,一個是針對線程的,一個是針對進程的。
/************************************************************************/ /* 硬件執行斷點,VEH */ /************************************************************************/ #pragma data_seg(".VehHardware") BOOL VehHardwarei = FALSE; DWORD Vehaddr; #pragma data_seg() #pragma comment(linker, "/section:.VehHardware,RWS") LONG NTAPI VehHardwareExceptionFilter( PEXCEPTION_POINTERS ExceptionInfo ) { if (ExceptionInfo->ContextRecord->Dr0 != 0 || ExceptionInfo->ContextRecord->Dr1 != 0 || ExceptionInfo->ContextRecord->Dr2 != 0 || ExceptionInfo->ContextRecord->Dr3 != 0) { VehHardwarei = TRUE; ExceptionInfo->ContextRecord->Dr0 = 0; ExceptionInfo->ContextRecord->Dr1 = 0; ExceptionInfo->ContextRecord->Dr2 = 0; ExceptionInfo->ContextRecord->Dr3 = 0; } // 設置新的eip 讓程序調轉到safe執行 ExceptionInfo->ContextRecord->Eip = Vehaddr; return EXCEPTION_CONTINUE_EXECUTION; } BOOL IsVehHardware() { PVOID VEHandle = ::AddVectoredExceptionHandler(1, VehHardwareExceptionFilter); _asm { mov Vehaddr, offset vehsafe; int 3; } vehsafe: if (VEHandle != NULL) { RemoveVectoredExceptionHandler(VEHandle); } return VehHardwarei; } void CAntiDebugDlg::OnBnClickedButton16() { // TODO: 在此添加控件通知處理程序代碼 char *p = "aaaaaaaaaaaaaaddddddddddddddbbbbbbbbbbbb"; if (IsVehHardware()) { AfxMessageBox(L"檢測到硬件執行斷點"); } else { AfxMessageBox(L"沒有檢測到硬件執行斷點"); } }
⑨利用異常處理 通過我們自己安裝異常處理例程,然后手動觸發異常,如果存在OD則不會走異常處理
/************************************************************************/ /* 異常處理判斷是否存在OD */ /************************************************************************/ #pragma data_seg(".excep") BOOL Exceptioni = FALSE; #pragma data_seg() #pragma comment(linker, "/section:.excep,RWS") LONG WINAPI ExceptionFilter( PEXCEPTION_POINTERS ExceptionInfo ) { Exceptioni = TRUE; //進行異常處理,則不存在OD return EXCEPTION_CONTINUE_EXECUTION; } BOOL IsException() { ULONG OldProtect = 0; LPTOP_LEVEL_EXCEPTION_FILTER lpsetun; lpsetun = ::SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ExceptionFilter); //安裝自己的異常處理 LPVOID pBuff = ::VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_READWRITE); *((PWORD)pBuff) = 0xc3; ::VirtualProtect(pBuff, 0x1000, PAGE_EXECUTE_READ | PAGE_GUARD, &OldProtect); _asm { call pBuff; //如果存在OD,會把這里的call 當成中斷處理,不會進行異常處理 } ::SetUnhandledExceptionFilter(lpsetun); //將原來的異常處理恢復頂層 return Exceptioni; } void CAntiDebugDlg::OnBnClickedButton13() { // TODO: 在此添加控件通知處理程序代碼 if (IsException()) { AfxMessageBox(L"沒有檢測到調試器"); } else { AfxMessageBox(L"檢測到調試器"); } }
⑩單步異常檢測
單步異常是調試器用來單步調試時使用,我們也可以用來檢測調試器的存在,由我們自己設置標志寄存器的值,觸發單步異常。如果存在調試器,這里的單步異常會由調試器進行處理,而不會走__except()異常處理,但這種檢測方法的局限性就在於檢測代碼需要被調試器單步調試的時候才有效。扯個題外話,這里我還打算嘗試一下C++11新支持的異常處理try{} /catch(){},沒想到一弄直接就崩潰了,還是老老實實用__try/__except吧!
/************************************************************************/ /* 單步異常檢測 */ /************************************************************************/ //這種方法需要下面的匯編代碼在單步跟中的時候才有效 void CAntiDebugDlg::OnBnClickedButton14() { // TODO: 在此添加控件通知處理程序代碼 __try { // 觸發單步異常 _asm { pushfd; //標志寄存器入棧 or dword ptr [esp], 100h; // 將 TF =1 單步 popfd; } AfxMessageBox(L"檢測到調試器"); //如果不觸發異常,說明有調試器 } //catch(...) 用C++ 11的catch()還是沒有原生__except強大,用catch(...)這里會崩潰 __except(EXCEPTION_EXECUTE_HANDLER) { AfxMessageBox(L"沒有檢測到調試器"); } }
11.into使調試器一直循環。使用溢出中斷,在沒有調試器的情況下會走異常處理,然后正常的執行下去,如果存在OD則會自己處理中斷,導致一直在這里循環。其實這里用into的溢出中斷和int 3的中斷效果是一樣的,這里采用的是平時使用較少的into溢出中斷。
#pragma data_seg(".VehInto") DWORD VehintoAddr = 0; #pragma data_seg() #pragma comment(linker, "/section:.VehInto,RWS") LONG NTAPI VehIntoExceptionFilter( PEXCEPTION_POINTERS ExceptionInfo ) { // 設置新的eip 讓程序調轉到safe執行 ExceptionInfo->ContextRecord->Eip = VehintoAddr; return EXCEPTION_CONTINUE_EXECUTION; } void IsVehIntoBreak() { PVOID VEHandle = ::AddVectoredExceptionHandler(1, VehIntoExceptionFilter); _asm { // 存放異常處理之后的偏移地址 mov VehintoAddr, offset Intosafe; mov ecx, 1; } here: _asm { /* 如果存在OD調試器,這里會一直循環下去。 */ rol ecx, 1; //不停的循環左移,直至產生溢出,OF位為1 into; //溢出中斷 ,會調用異常處理 jmp here; } Intosafe: if (VEHandle != NULL) { RemoveVectoredExceptionHandler(VEHandle); } } void CAntiDebugDlg::OnBnClickedButton15() { // TODO: 在此添加控件通知處理程序代碼 IsVehIntoBreak(); }
12 . int 2d
/************************************************************************/ /* INT 2D */ /************************************************************************/ BOOL IsInt2d() { __try { __asm { int 2dh; inc eax;//any opcode of singlebyte.如果開啟VT會崩潰 //;or u can put some junkcode,"0xc8"..."0xc2"..."0xe8"..."0xe9" } return TRUE; } __except(EXCEPTION_EXECUTE_HANDLER) { return FALSE; } } void CAntiDebugDlg::OnBnClickedButton21() { // TODO: 在此添加控件通知處理程序代碼 if (IsInt2d()) { AfxMessageBox(L"檢測到調試器"); } else { AfxMessageBox(L"沒有檢測到調試器"); } }
13. 檢驗內存校驗和
調試器(OD)在設置斷點會改變內存屬性,改寫為0xCC,我們需要檢驗自身的某一個段內存的校驗和就可以判斷是否有調試器的存在
/************************************************************************/ /* CheckSum */ /************************************************************************/ BOOL CheckSum() { BOOL bFoundOD; bFoundOD = FALSE; DWORD CHECK_SUM = 5555; //正確校驗值 DWORD dwAddr; dwAddr = (DWORD)CheckSum; __asm { ; 檢測代碼開始 mov esi, dwAddr; mov ecx, 100; xor eax, eax; checksum_loop: movzx ebx, byte ptr [esi]; add eax, ebx; rol eax, 1; inc esi; loop checksum_loop; cmp eax, CHECK_SUM; jz ODNotFound; mov bFoundOD, 1; ODNotFound: } return bFoundOD; } void CAntiDebugDlg::OnBnClickedButton27() { // TODO: 在此添加控件通知處理程序代碼 if (CheckSum()) { AfxMessageBox(L"檢測到調試器"); } else { AfxMessageBox(L"沒有檢測到調試器"); } }
14. 保護頁異常
當執行一個屬性為PAGE_GUARD的頁面時,觸發EXCEPTION_GUARD_PAGE的異常,如果存在調試器一般都是由調試器處理了。
/************************************************************************/ /* “保護頁異常” */ /************************************************************************/ //當應用程序嘗試執行保護頁內的代碼時,將會產生一個EXCEPTION_GUARD_PAGE(0x80000001)異常 BOOL IsGuardPages() { SYSTEM_INFO sSysInfo; DWORD dwPageSize = 0; DWORD OldProtect = 0; GetSystemInfo(&sSysInfo); dwPageSize = sSysInfo.dwPageSize; LPVOID lpvBase = VirtualAlloc(NULL, dwPageSize, MEM_COMMIT, PAGE_READWRITE); if (lpvBase == NULL) { return FALSE; } PBYTE lptmpB = (PBYTE)lpvBase; *lptmpB = 0xc3; //retn VirtualProtect(lpvBase, dwPageSize, PAGE_EXECUTE_READ | PAGE_GUARD, &OldProtect); __try { __asm call dword ptr[lpvBase]; VirtualFree(lpvBase, 0, MEM_RELEASE); return TRUE; } __except(EXCEPTION_EXECUTE_HANDLER) { VirtualFree(lpvBase, 0, MEM_RELEASE); return FALSE; } } void CAntiDebugDlg::OnBnClickedButton25() { // TODO: 在此添加控件通知處理程序代碼 if (IsGuardPages()) { AfxMessageBox(L"檢測到調試器"); } else { AfxMessageBox(L"沒有檢測到調試器"); } }
14. 還有就是關於調試對象的檢測,這里就牽扯到內核層的問題,一些基礎內容就不進行贅述了。如果往深了研究可以學習系統的內核調試引擎,折騰TX的游戲TP反雙機調試。這里只給出一些在應用層利用的比較簡單的檢測方法。
檢測調試對象DebugObject
/************************************************************************/ /* ZwQueryObject */ /************************************************************************/ #ifndef STATUS_INFO_LENGTH_MISMATCH #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) #endif void CAntiDebugDlg::OnBnClickedButton24() { POBJECT_TYPES_INFORMATION pTypesInfo = NULL; ULONG dwSize = 0; PFNZwQueryObject pfunc_ZwQueryObject; HMODULE hModule = LoadLibrary(L"ntdll.dll"); pfunc_ZwQueryObject = (PFNZwQueryObject)GetProcAddress(hModule, "ZwQueryObject"); if (pfunc_ZwQueryObject) { NTSTATUS Status = pfunc_ZwQueryObject( NULL, ObjectAllTypesInformation, NULL, 0, &dwSize ); if (Status == STATUS_INFO_LENGTH_MISMATCH) { pTypesInfo = (POBJECT_TYPES_INFORMATION)VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_READWRITE); if (pTypesInfo == NULL) return; Status = pfunc_ZwQueryObject( NULL, ObjectAllTypesInformation, pTypesInfo, dwSize, &dwSize ); if (!NT_SUCCESS(Status)) { AfxMessageBox(L"查詢錯誤"); VirtualFree (pTypesInfo, 0, MEM_RELEASE); return; } for (UINT i = 0; i < pTypesInfo->NumberOfTypes; i++) { if (pTypesInfo->TypeInformation[i].TypeName.Buffer != NULL) { if (_wcsicmp(pTypesInfo->TypeInformation[i].TypeName.Buffer, L"DebugObject") == 0) { AfxMessageBox(L"發現OD"); VirtualFree (pTypesInfo, 0, MEM_RELEASE); return; } } } AfxMessageBox(L"沒有OD!"); VirtualFree (pTypesInfo, 0, MEM_RELEASE); } } }
DebugObjectHandle
/************************************************************************/ /* ProcessDebugObjectHandle */ /************************************************************************/ void CAntiDebugDlg::OnBnClickedButton22() { // TODO: 在此添加控件通知處理程序代碼 HANDLE hProcess = NULL; DWORD dwResult; PFNZwQueryInformationProcess pfunc_ZwQueryInformationProcess; HMODULE hModule = LoadLibrary(L"ntdll.dll"); pfunc_ZwQueryInformationProcess = (PFNZwQueryInformationProcess)GetProcAddress(hModule, "ZwQueryInformationProcess"); pfunc_ZwQueryInformationProcess( GetCurrentProcess(), ProcessDebugObjectHandle, &dwResult, 4, NULL ); if (dwResult != 0) { AfxMessageBox(L"檢測到調試器"); } else { AfxMessageBox(L"沒有檢測到調試器"); } }
一篇關於Anti不錯的文章,不過是英文的,可以參考:anti-unpackers.pdf
http://www.openrce.org/reference_library/anti_reversing_view/34/INT%202D%20Debugger%20Detection/