原來... 反調試技術揭祕(轉)

在調試一些病毒程序的時候,可能會碰到一些反調試技術,也就是說,被調試的程序能夠檢測到本身是否被調試器附加了,若是探知本身正在被調試,確定是有人試圖反彙編啦之類的方法破0解本身。爲了瞭解如何破0解反調試技術,首先咱們來看看反調試技術。
 
1、Windows API方法
 
Win32提供了兩個API, IsDebuggerPresent和CheckRemoteDebuggerPresent能夠用來檢測當前進程是否正在被調試,以IsDebuggerPresent函數爲例,例子以下:
 
BOOL ret = IsDebuggerPresent();
printf("ret = %d\n", ret);
 
破0解方法很簡單,就是在系統裏將這兩個函數hook掉,讓這兩個函數一直返回false就能夠了,網上有不少作hook API工做的工具,也有不少工具源代碼是開放的,因此這裏就不細談了。
 
 
2、查詢進程PEB的BeingDebugged標誌位
 
當進程被調試器所附加的時候,操做系統會自動設置這個標誌位,所以在程序裏按期查詢這個標誌位就能夠了,例子以下:
 
bool PebIsDebuggedApproach()
{
       char result = 0;
       __asm
       {
// 進程的PEB地址放在fs這個寄存器位置上
              mov eax, fs:[30h]
// 查詢BeingDebugged標誌位
              mov al, BYTE PTR [eax + 2] 
              mov result, al
       }
 
       return result != 0;
}
 
 
3、查詢進程PEB的NtGlobal標誌位 
 
跟第二個方法同樣,當進程被調試的時候,操做系統除了修改BeingDebugged這個標誌位之外,還會修改其餘幾個地方,其中NtDll中一些控制堆(Heap)操做的函數的標誌位就會被修改,所以也能夠查詢這個標誌位,例子以下:
 
bool PebNtGlobalFlagsApproach()
{
       int result = 0;
 
       __asm
       {
  // 進程的PEB
              mov eax, fs:[30h]
  // 控制堆操做函數的工做方式的標誌位
              mov eax, [eax + 68h]
  // 操做系統會加上這些標誌位FLG_HEAP_ENABLE_TAIL_CHECK, 
  // FLG_HEAP_ENABLE_FREE_CHECK and FLG_HEAP_VALIDATE_PARAMETERS,
  // 它們的並集就是x70
  //
  // 下面的代碼至關於C/C++的
  //     eax = eax & 0x70
              and eax, 0x70
              mov result, eax
       }
 
       return result != 0;
}
 
 
4、查詢進程堆的一些標誌位
 
這個方法是第三個方法的變種,只要進程被調試,進程在堆上分配的內存,在分配的堆的頭信息裏,ForceFlags這個標誌位會被修改,所以能夠經過判斷這個標誌位的方式來反調試。由於進程能夠有不少的堆,所以只要檢查任意一個堆的頭信息就能夠了,因此這個方法貌似很強大,例子以下:
 
bool HeapFlagsApproach()
{
       int result = 0;
 
       __asm
       {
      // 進程的PEB
              mov eax, fs:[30h]
      // 進程的堆,咱們隨便訪問了一個堆,下面是默認的堆
              mov eax, [eax + 18h]
  // 檢查ForceFlag標誌位,在沒有被調試的狀況下應該是
              mov eax, [eax + 10h]
              mov result, eax
       }
 
       return result != 0;
}
 
 
5、使用NtQueryInformationProcess函數
 
NtQueryInformationProcess函數是一個未公開的API,它的第二個參數能夠用來查詢進程的調試端口。若是進程被調試,那麼返回的端口值會是-1,不然就是其餘的值。因爲這個函數是一個未公開的函數,所以須要使用LoadLibrary和GetProceAddress的方法獲取調用地址,示例代碼以下:
 
// 聲明一個函數指針。
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");
       else
              return debugPort == -1;
 
       return false;
}
 
 
6、NtSetInformationThread方法
 
這個也是使用Windows的一個未公開函數的方法,你能夠在當前線程裏調用NtSetInformationThread,調用這個函數時,若是在第二個參數裏指定0x11這個值(意思是ThreadHideFromDebugger),等於告訴操做系統,將全部附加的調試器通通取消掉。示例代碼:
 
// 聲明一個函數指針。
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);
}
 
 
7、觸發異常的方法
 
這個技術的原理是,首先,進程使用SetUnhandledExceptionFilter函數註冊一個未處理異常處理函數A,若是進程沒有被調試的話,那麼觸發一個未處理異常,會致使操做系統將控制權交給先前註冊的函數A;而若是進程被調試的話,那麼這個未處理異常會被調試器捕捉,這樣咱們的函數A就沒有機會運行了。
 
這裏有一個技巧,就是觸發未處理異常的時候,若是跳轉回原來代碼繼續執行,而不是讓操做系統關閉進程。方案是在函數A裏修改eip的值,由於在函數A的參數_EXCEPTION_POINTERS裏,會保存當時觸發異常的指令地址,因此在函數A里根據這個指令地址修改寄存器eip的值就能夠了,示例代碼以下:
 
// 進程要註冊的未處理異常處理程序A
LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *pei)
{
       SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)
              pei->ContextRecord->Eax);
       // 修改寄存器eip的值
       pei->ContextRecord->Eip += 2;
       // 告訴操做系統,繼續執行進程剩餘的指令(指令保存在eip裏),而不是關閉進程
       return EXCEPTION_CONTINUE_EXECUTION;
}
 
bool UnhandledExceptionFilterApproach()
{
       SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
       __asm
       {
              // 將eax清零
              xor eax, eax
              // 觸發一個除零異常
              div eax
       }
 
       return false;
}
 
8、調用DeleteFiber函數
 
若是給DeleteFiber函數傳遞一個無效的參數的話,DeleteFiber函數除了會拋出一個異常之外,仍是將進程的LastError值設置爲具體出錯緣由的代號。然而,若是進程正在被調試的話,這個LastError值會被修改,所以若是調試器繞過了第七步裏講的反調試技術的話,咱們還能夠經過驗證LastError值是否是被修改過來檢測調試器的存在,示例代碼:
 
bool DeleteFiberApproach()
{
       char fib[1024] = {0};
       // 會拋出一個異常並被調試器捕獲
       DeleteFiber(fib);
 
       // 0x57的意思是ERROR_INVALID_PARAMETER
       return (GetLastError() != 0x57);
}
相關文章
相關標籤/搜索