反調原理
該檢測方式使用 IsDebuggerPresent 或者 CheckRemoteDebuggerPresent 函數,這兩個函數都是 kernel32.dll 中實現的
對于 IsDebuggerPresent 函數,如果返回值為 TRUE,那么表示當前進程在調試器上下文中運行
CheckRemoteDebuggerPresent 的底層是通過 NtQueryInformationProcess 函數來實現的,函數接受一個 BOOL 的指針參作為返回值
函數調用成功的時候返回非零值,這時候檢查出參 pbDebuggerPresent 指針的值,如果值為 TRUE,那么表示當前進程正在被調試中
使用這兩個函數需要引用 windows.h 頭文件,函數的原型分別如下
BOOL IsDebuggerPresent();BOOL CheckRemoteDebuggerPresent(HANDLE hProcess, PBOOL pbDebuggerPresent);
IsDebuggerPresent 實際上是查詢當前進程的 PEB(Process Environment Block)中的 BeingDebugged 字段
PEB 結構是進程環境塊,是 Windows 內核中一個核心的內部數據結構,包含了進程的大量信息,在 Windows SDK
的 winternl.h 提供了部分定義
PEB 的結構如下,IsDebuggerPresent 使用了第二個字段 BeingDebugged,當被調試的時候該字段值被置為 1
typedef struct _PEB {BYTE Reserved1[2];BYTE BeingDebugged;BYTE Reserved2[1];PVOID Reserved3[2];PPEB_LDR_DATA Ldr;PRTL_USER_PROCESS_PARAMETERS ProcessParameters;PVOID Reserved4[3];PVOID AtlThunkSListPtr;PVOID Reserved5;ULONG Reserved6;PVOID Reserved7;ULONG Reserved8;ULONG AtlThunkSListPtr32;PVOID Reserved9[45];BYTE Reserved10[96];PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;BYTE Reserved11[128];PVOID Reserved12[1];ULONG SessionId;
} PEB, *PPEB;
實現代碼
完整的反調試的實現代碼如下
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <windows.h>
#include <TlHelp32.h>
#include <winternl.h>bool CheckProcessIsDebuging()
{//return IsDebuggerPresent();BOOL isDebugging = FALSE;HANDLE hHandle = GetCurrentProcess();CheckRemoteDebuggerPresent(hHandle, &isDebugging);return isDebugging;
}void ThreadProc()
{while (true){if (CheckProcessIsDebuging()){std::cout << "Debugging..." << std::endl;}else{std::cout << "Running..." << std::endl;}std::this_thread::sleep_for(std::chrono::milliseconds(1000));}
}int main()
{std::thread thrd(ThreadProc);thrd.join();return 0;
}
逆向處理
對于 IsDebuggerPresent 的繞過
x64dbg 附加載入 anti04.exe 后,在符號窗口中選中主模塊,然后右側查看到模塊,顯示它的一個導入函數 IsDebuggerPresent
選定 kernel32.IsDebuggerPresent 然后右鍵 “在內存窗口中轉到”,內存窗口中顯示了 IAT 表的該函數的導入項
內存的值即是 kernel32.IsDebuggerPresent 函數的實際地址,選擇該內存地址,右鍵 “搜索引用”
然后在引用頁面中雙擊引用記錄跳轉到 IsDebuggerPresent 函數的調用處,然后在該 call 處進行下斷點
這里的繞過方式有兩種,一個是在 call 之后 test 前修改 eax 的值為 0,但是這個只對當前的一次 call 調用生效
另一個是直接修改 peb 中的 BeingDebugged 字段值,需要先定位到 peb 的內存地址,然后再進行修改
關于 peb 地址是固定的 gs:[60],其中 gs 是 TEB 的段寄存器,gs:[0x60] 是 TEB + 0x60 偏移處的值,是當前線程所屬進程的 PEB 的地址
在左下角的命令框中輸入 gs:[60]
后回車,然后下方顯示出 peb 的內存地址,點擊該地址在內存窗口中進行跳轉
然后在該地址中,修改第三個字節為 0(未修改前為 1 表示在調試中),之后的運行就會顯示程序未處于調試中了
在 x64dbg 中,可以通過 ScyllaHide 插件進行繞過,安裝插件后,默認就已經勾選了對 BeingDebugged 的繞過
這里主要是學習反調試的基本原理,在此不會對插件的使用進行過多的說明
對于 CheckRemoteDebuggerPresent 的繞過
在 cpu 主窗口中 ctrl + G
輸入 CheckRemoteDebuggerPresent 進行搜索,得到下面的地址
選中該行記錄 entry 跳轉到 CheckRemoteDebuggerPresent 函數的匯編代碼位置
下面這兩行的 ds:[rdi]
就是函數的第二個參數 pbDebuggerPresent,ebx = 0 或 1(是否檢測到調試器)
setne bl
會根據前面 cmp 判斷是否寫入 bl = 1
,因此將這句匯編進行覆蓋即可,覆蓋為清空 ebx
00007FF89ECC7B60 | 0F95C3 | setne bl |
00007FF89ECC7B63 | 891F | mov dword ptr ds:[rdi],ebx |
覆蓋后的代碼顯示如下,這時候就可以實現了對 CheckRemoteDebuggerPresent 的繞過