反调试技术揭秘(转)
?
?
在調(diào)試一些病毒程序的時(shí)候,可能會(huì)碰到一些反調(diào)試技術(shù),也就是說,被調(diào)試的程序可以檢測到自己是否被調(diào)試器附加了,如果探知自己正在被調(diào)試,肯定是有人試圖反匯編之類的方法破解自己。為了了解如何破解反調(diào)試技術(shù),首先我們來看看反調(diào)試技術(shù)。
?
?
?
一、Windows API 方法
?
Win32 提供了兩個(gè) API,?IsDebuggerPresent 和 CheckRemoteDebuggerPresent 可以用來檢測當(dāng)前進(jìn)程是否正在被調(diào)試,以IsDebuggerPresent 函數(shù)為例,例子如下:
BOOL ret = IsDebuggerPresent();printf("ret = %d\n", ret);破解方法很簡單,就是在系統(tǒng)里將這兩個(gè)函數(shù) hook 掉,讓這兩個(gè)函數(shù)一直返回 false 就可以了,網(wǎng)上有很多做 hook API 工作的工具,也有很多工具源代碼是開放的,所以這里就不細(xì)談了。
?
?
?
二、查詢進(jìn)程 PEB 的 BeingDebugged 標(biāo)志位
?
當(dāng)進(jìn)程被調(diào)試器所附加的時(shí)候,操作系統(tǒng)會(huì)自動(dòng)設(shè)置這個(gè)標(biāo)志位,因此在程序里定期查詢這個(gè)標(biāo)志位就可以了,例子如下:
bool PebIsDebuggedApproach() {char result = 0;__asm{// 進(jìn)程的PEB地址放在fs這個(gè)寄存器位置上mov eax, fs:[30h]// 查詢BeingDebugged標(biāo)志位mov al, BYTE PTR [eax + 2]?mov result, al}return result != 0; }?
?
?
三、查詢進(jìn)程 PEB 的 NtGlobal 標(biāo)志位?
?
跟第二個(gè)方法一樣,當(dāng)進(jìn)程被調(diào)試的時(shí)候,操作系統(tǒng)除了修改 BeingDebugged 這個(gè)標(biāo)志位以外,還會(huì)修改其他幾個(gè)地方,其中NtDll 中一些控制堆(Heap)操作的函數(shù)的標(biāo)志位就會(huì)被修改,因此也可以查詢這個(gè)標(biāo)志位,例子如下:
bool PebNtGlobalFlagsApproach() {int result = 0;__asm{// 進(jìn)程的PEBmov eax, fs:[30h]// 控制堆操作函數(shù)的工作方式的標(biāo)志位mov eax, [eax + 68h]// 操作系統(tǒng)會(huì)加上這些標(biāo)志位FLG_HEAP_ENABLE_TAIL_CHECK,?// FLG_HEAP_ENABLE_FREE_CHECK and FLG_HEAP_VALIDATE_PARAMETERS,// 它們的并集就是x70//// 下面的代碼相當(dāng)于C/C++的// eax = eax & 0x70and eax, 0x70mov result, eax}return result != 0;}
?
?
四、查詢 進(jìn)程堆 的一些 標(biāo)志位
?
這個(gè)方法是第三個(gè)方法的變種,只要進(jìn)程被調(diào)試,進(jìn)程在堆上分配的內(nèi)存,在分配的堆的頭信息里,ForceFlags 這個(gè)標(biāo)志位會(huì)被修改,因此可以通過判斷這個(gè)標(biāo)志位的方式來反調(diào)試。因?yàn)檫M(jìn)程可以有很多的堆,因此只要檢查任意一個(gè)堆的頭信息就可以了,所以這個(gè)方法貌似很強(qiáng)大,例子如下:
bool HeapFlagsApproach() {int result = 0;__asm{// 進(jìn)程的PEBmov eax, fs:[30h]// 進(jìn)程的堆,我們隨便訪問了一個(gè)堆,下面是默認(rèn)的堆mov eax, [eax + 18h]// 檢查ForceFlag標(biāo)志位,在沒有被調(diào)試的情況下應(yīng)該是mov eax, [eax + 10h]mov result, eax}return result != 0; }?
?
?
五、使用 NtQueryInformationProcess 函數(shù)
?
NtQueryInformationProcess 函數(shù)是一個(gè)未公開的 API,它的第二個(gè)參數(shù)可以用來查詢進(jìn)程的調(diào)試端口。如果進(jìn)程被調(diào)試,那么返回的端口值會(huì)是 -1,否則就是其他的值。由于這個(gè)函數(shù)是一個(gè)未公開的函數(shù),因此需要使用 LoadLibrary 和 GetProceAddress的方法獲取調(diào)用地址,示例代碼如下:
// 聲明一個(gè)函數(shù)指針。typedef NTSTATUS (WINAPI *NtQueryInformationProcessPtr)(HANDLE processHandle,PROCESSINFOCLASS processInformationClass,PVOID processInformation,ULONG processInformationLength,PULONG returnLength);bool NtQueryInformationProcessApproach() {int debugPort = 0;HMODULE hModule = LoadLibrary(TEXT("Ntdll.dll "));NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess");if ( NtQueryInformationProcess(GetCurrentProcess(), (PROCESSINFOCLASS)7, &debugPort, sizeof(debugPort), NULL) )printf("[ERROR NtQueryInformationProcessApproach] NtQueryInformationProcess failed\n");elsereturn debugPort == -1;return false;}?
?
?
六、NtSetInformationThread 方法
?
這個(gè)也是使用 Windows 的一個(gè)未公開函數(shù)的方法,你可以在當(dāng)前線程里調(diào)用 NtSetInformationThread,調(diào)用這個(gè)函數(shù)時(shí),如果在第二個(gè)參數(shù)里指定 0x11 這個(gè)值(意思是 ThreadHideFromDebugger ),等于告訴操作系統(tǒng),將所有附加的調(diào)試器統(tǒng)統(tǒng)取消掉。示例代碼:
// 聲明一個(gè)函數(shù)指針。typedef NTSTATUS (*NtSetInformationThreadPtr)(HANDLE threadHandle,THREADINFOCLASS threadInformationClass,PVOID threadInformation,ULONG threadInformationLength);void NtSetInformationThreadApproach() {HMODULE hModule = LoadLibrary(TEXT("ntdll.dll"));NtSetInformationThreadPtr NtSetInformationThread = (NtSetInformationThreadPtr)GetProcAddress(hModule, "NtSetInformationThread");NtSetInformationThread(GetCurrentThread(), (THREADINFOCLASS)0x11, 0, 0);}?
?
?
七、觸發(fā)異常的方法
?
這個(gè)技術(shù)的原理是,首先,進(jìn)程使用 SetUnhandledExceptionFilter 函數(shù)注冊(cè)一個(gè)未處理異常處理 函數(shù)A,如果進(jìn)程沒有被調(diào)試的話,那么觸發(fā)一個(gè)未處理異常,會(huì)導(dǎo)致操作系統(tǒng)將控制權(quán)交給先前注冊(cè)的函數(shù)A;而如果進(jìn)程被調(diào)試的話,那么這個(gè)未處理異常會(huì)被調(diào)試器捕捉,這樣我們的 函數(shù)A 就沒有機(jī)會(huì)運(yùn)行了。
這里有一個(gè)技巧,就是觸發(fā)未處理異常的時(shí)候,如果跳轉(zhuǎn)回原來代碼繼續(xù)執(zhí)行,而不是讓操作系統(tǒng)關(guān)閉進(jìn)程。方案是在函數(shù)A里修改eip的值,因?yàn)樵?函數(shù)A 的 參數(shù) _EXCEPTION_POINTERS 里,會(huì)保存當(dāng)時(shí)觸發(fā)異常的指令地址,所以在 函數(shù)A 里根據(jù)這個(gè)指令地址修改 寄存器eip 的值就可以了,示例代碼如下:
// 進(jìn)程要注冊(cè)的未處理異常處理程序A LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *pei) {SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)pei->ContextRecord->Eax);// 修改寄存器eip的值pei->ContextRecord->Eip += 2;// 告訴操作系統(tǒng),繼續(xù)執(zhí)行進(jìn)程剩余的指令(指令保存在eip里),而不是關(guān)閉進(jìn)程return EXCEPTION_CONTINUE_EXECUTION; }bool UnhandledExceptionFilterApproach() {SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);__asm{// 將eax清零xor eax, eax// 觸發(fā)一個(gè)除零異常div eax}return false;}?
?
?
八、調(diào)用 DeleteFiber 函數(shù)
?
如果給 DeleteFiber 函數(shù)傳遞一個(gè)無效的參數(shù)的話,DeleteFiber函數(shù)除了會(huì)拋出一個(gè)異常以外,還是將進(jìn)程的LastError值設(shè)置為具體出錯(cuò)原因的代號(hào)。然而,如果進(jìn)程正在被調(diào)試的話,這個(gè)LastError值會(huì)被修改,因此如果調(diào)試器繞過了第七步里講的反調(diào)試技術(shù)的話,我們還可以通過驗(yàn)證LastError值是不是被修改過來檢測調(diào)試器的存在,示例代碼:
bool DeleteFiberApproach() {char fib[1024] = {0};// 會(huì)拋出一個(gè)異常并被調(diào)試器捕獲DeleteFiber(fib);// 0x57的意思是ERROR_INVALID_PARAMETERreturn (GetLastError() != 0x57); }?
?
?
總結(jié)
以上是生活随笔為你收集整理的反调试技术揭秘(转)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java8 Stream详解~收集(co
- 下一篇: 跳石板(通俗易懂的思路和方法)