反調試與反反調試
什么是反調試? 什么是反反調試?
靜態反調試
-
特點:一般在調試開始時阻攔調試者,調試只需要找到原因后就可以一次性突破
-
PEB
⭐️BeginDebug
: 調試標志位
// 通過檢查 PEB.BeingDebuged 字段判斷是否被調試 // 可以在目標程序運行之前修改對應的字段為 0 進行反反調試 bool CheckBeingDebugged() { __asm { ; 獲取到 PEB 的內容 mov eax, fs:[0x30] ; 獲取 PEB 內偏移為 2 大小為 1 字節的字段 movzx eax, byte ptr[eax + 2] } } int main() { if (CheckBeingDebugged()) printf("當前處於[被]調試狀態\n"); else printf("當前處於[非]調試狀態\n"); system("pause"); return 0; }
// 使用 IsDebuggerPresent 原理同樣是判斷 PEB.BeingDebuged // 通過 HOOK 函數和修改對應字段的方式可以進行反調試 int main() { if (IsDebuggerPresent()) printf("當前處於[被]調試狀態\n"); else printf("當前處於[非]調試狀態\n"); system("pause"); return 0; }
Ldr
內存狀態Heap
(Flags
,Force Flags
): 堆狀態NtGlobalFlag
: 內核全局標記- 通過
PEB.NtGlobalFlag
判斷是否被調試,當處於被調試狀態時PEB.NtGlobalFlag
保存的是0x70
,可以通過修改標志反反調試
- 通過
bool CheckNtGlobalFlag() { __asm { ; 獲取到 PEB, 保存在 FS :[0x30] mov eax, fs : [0x30] ;獲取到 NtGlobalFlag 字段的內容 mov eax, [eax + 0x68] } } int main() { if (CheckNtGlobalFlag()) printf("當前處於[被]調試狀態\n"); else printf("當前處於[非]調試狀態\n"); system("pause"); return 0; }
-
TEB
StaticUnicodeString
:靜態緩沖區
-
使用原始
API
NtQuerySystemInformationProcess()
ProcessDebugPort(0x07)
: 獲取調試端口ProcessDebugObjectHandle(0x1E)
: 獲取調試句柄ProcessDebugFlag(0x1F)
: 獲取調試標記
NtQuerySystemInformation()
:SystemKernelDeBuggerInformation(0x23)
:獲取系統調試狀態 (雙機)
NtQueryObject()
: 遍歷系統內核
-
攻擊調試器
NtSetInformationThread()
ThreadHideFormDebugger(0x11)
-
打開進程檢查
SetDebugPrivilege
➡️ 檢查進程是否具有調試權限
-
利用
TLS
回調函數 -
使用普通
API
- 父進程的檢查
- 如果一個進程被正常打開,那么它的父進程應該是
Explorer.exe
,也就是資源管理器,如果判斷不是,就可能被調試,提供參考。
- 如果一個進程被正常打開,那么它的父進程應該是
bool CheckParentProcess() { struct PROCESS_BASIC_INFORMATION { ULONG ExitStatus; // 進程返回碼 PPEB PebBaseAddress; // PEB 地址 ULONG AffinityMask; // CPU 親和性掩碼 LONG BasePriority; // 基本優先級 ULONG UniqueProcessId; // 本進程PID ULONG InheritedFromUniqueProcessId; // 父進程PID }stcProcInfo; // 查詢到目標進程的對應信息,主要是 父進程 ID NtQueryInformationProcess( GetCurrentProcess(), ProcessBasicInformation, &stcProcInfo, sizeof(stcProcInfo), NULL); // 查詢資源管理器對應的 PID DWORD ExplorerPID = 0; DWORD CurrentPID = stcProcInfo.InheritedFromUniqueProcessId; GetWindowThreadProcessId(FindWindow(L"Progman", NULL), &ExplorerPID); // 比對兩個 PID 的值,相同就OK,不同就可能被調試 return ExplorerPID == CurrentPID ? false : true; } int main() { if (CheckParentProcess()) printf("當前處於[被]調試狀態\n"); else printf("當前處於[非]調試狀態\n"); system("pause"); return 0; }
- 窗口名檢查
- 原理是通過查找窗口類或窗口名稱對應的窗口是否存在來進行調試器或其它分析工具的檢查。[缺點是窗口的的名字通常不是固定的,檢查起來非常的不方便]
- 還可以通過遍歷進程的方式來檢查調試器或其它工具是否存在。[缺點是可以通過修改 exe 的名字修改進程名]
int main() { if (FindWindow(NULL, L"OllyDbg")) printf("存在調試器\n"); else printf("沒檢測到調試器\n"); return 0; }
- 進程名檢查
- 原理就是查詢
EPROCESS
結構體中相關的字段,因為不能在R3
修改R0
的數據,所以只能HOOK
- 原理就是查詢
bool CheckProcessDebugPort() { int nDebugPort = 0; NtQueryInformationProcess( GetCurrentProcess(), // 目標進程句柄 ProcessDebugPort, // 查詢信息類型(7) &nDebugPort, // 輸出查詢信息 sizeof(nDebugPort), // 查詢類型大小 NULL); // 實際返回數據大小 // 如果返回的是 -1 ,那么就被調試了 return nDebugPort == 0xFFFFFFFF ? true : false; } int main() { if (CheckProcessDebugPort()) printf("當前處於[被]調試狀態\n"); else printf("當前處於[非]調試狀態\n"); system("pause"); return 0; }
- 文件名及文件路徑檢查
- 注冊表檢查
- 。。。
- 父進程的檢查
動態反調試
- 特點:一般在調試的過程中阻攔調試者,可在調試的過程中被頻繁觸發因此需要調試者隨時關注
- 使用
SEH
- 異常
- 斷點
SetUnhandleedExceptionFilter()
- 時間檢查
- 單步檢查
- 補丁檢查
- 反反匯編
- 偷取代碼
- 分頁保護
- 殼
- 虛擬機
OllyDbg
插件編寫
- 插件通常是以什么樣的方式存在的?
通常以DLL
的方式存在的,但是后綴名可能會有變化
- 應用程序如何找到所有的插件?
所有的插件都應該被保護到具體的路徑中
- 應用程序怎樣知道是否符合自己的要求?
- 插件必須提供指定的名稱的函數說明自己的信息,插件還需要通過對應名稱的函數提供具體的功能