[逆向工程] C實現過程調試與鉤子安裝(二十七)
引言
在現代逆向工程和調試領域,能夠動態監控和操控進程執行非常關鍵。本篇文章將全面講解如何使用 C 編寫一個進程調試器——hookdbg64.exe
,實現對目標進程的附加、監控 WriteFile
函數的調用,并動態安裝和卸載鉤子。本文將涵蓋原理分析、關鍵代碼實現、實際調試以及注意事項,便于讀者深刻理解和掌握相關技術。
測試:
一、資源準備
1. 資源準備
- gmp.exe:目標程序(可測試的目標進程)
- hookdbg64.exe:自制調試器,運行時將其連接至目標程序
- gcc:用于編譯源代碼的編譯器,確保已預先安裝
2. 任務目標
通過運行 hookdbg64.exe
將其附加到 gmp.exe
進程,捕獲其 WriteFile
函數的調用,并在需要時可以卸載鉤子,實現對該調用的動態調試和監控。
二、鉤子的核心原理
鉤子的核心原理是操作系統的進程調試和內存管理機制。
1. 操作系統角度
-
調試權限:操作系統通過權限控制,確保只有獲得調試權限的進程能調試其他進程。在我們的代碼中,通過
SetDebugPrivilege
函數申請調試權限。 -
內存保護:使用
VirtualProtectEx
函數調整進程的內存保護屬性,這是安裝鉤子的基礎。
2. 程序運行機制
-
DLL 入口函數:鉤子通常利用 DLL 的
DllMain
入口函數進行初始化和清理,但在此實現中,我們直接修改內存內容。 -
調試事件處理:鉤子安裝后,目標進程將觸發調試事件,如進程創建、異常、單步執行等,使用
WaitForDebugEvent
和狀態機模式處理這些事件。
三、完整 C 實現代碼
以下是 hookdbg64.c
的全部代碼實現,該程序通過調試和鉤子監控目標進程的特定函數調用。
//======hookdbg64.c======
#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_WARNINGS#include <windows.h>
#include <stdio.h>// 全局變量
LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_Cpdi;
BYTE g_chINT3 = 0xCC, g_chOrgByte = 0;
BOOL g_bFirstBreakpoint = TRUE;
BOOL g_bHooked = FALSE;// 函數聲明
void DebugLoop();
BOOL SetDebugPrivilege(BOOL bEnable);
BOOL InstallHook();
BOOL UninstallHook();
BOOL HandleSingleStepException(DWORD dwThreadId);
BOOL AdjustMemoryProtection(HANDLE hProcess, LPVOID address, SIZE_T size, DWORD* oldProtect);int main(int argc, char* argv[])
{DWORD dwPID;if (argc != 2) {printf("\nUSAGE: hookdbg64.exe <pid>\n");return 1;}// 獲取目標進程IDdwPID = atoi(argv[1]);// 設置調試權限if (!SetDebugPrivilege(TRUE)) {printf("Warning: Could not set debug privilege. Error: %d\n", GetLastError());}// 附加到目標進程if (!DebugActiveProcess(dwPID)) {printf("DebugActiveProcess(%d) failed! Error Code: %d\n", dwPID, GetLastError());return 1;}printf("Debugger attached to PID: %d\n", dwPID);// 進入調試循環DebugLoop();// 清理資源if (g_Cpdi.hProcess) CloseHandle(g_Cpdi.hProcess);if (g_Cpdi.hThread) CloseHandle(g_Cpdi.hThread);return 0;
}// 設置內存保護屬性
BOOL AdjustMemoryProtection(HANDLE hProcess, LPVOID address, SIZE_T size, DWORD* oldProtect)
{return VirtualProtectEx(hProcess, address, size, PAGE_EXECUTE_READWRITE, oldProtect);
}// 設置調試權限
BOOL SetDebugPrivilege(BOOL bEnable)
{HANDLE hToken;TOKEN_PRIVILEGES tp;LUID luid;if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {printf("OpenProcessToken failed. Error: %d\n", GetLastError());return FALSE;}if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {printf("LookupPrivilegeValue failed. Error: %d\n", GetLastError());CloseHandle(hToken);return FALSE;}tp.PrivilegeCount = 1;tp.Privileges[0].Luid = luid;tp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {printf("AdjustTokenPrivileges failed. Error: %d\n", GetLastError());CloseHandle(hToken);return FALSE;}CloseHandle(hToken);return TRUE;
}// 安裝鉤子
BOOL InstallHook()
{if (!g_pfWriteFile || !g_Cpdi.hProcess) {printf("Invalid parameters for InstallHook\n");return FALSE;}DWORD oldProtect;// 調整內存保護if (!AdjustMemoryProtection(g_Cpdi.hProcess, g_pfWriteFile, sizeof(BYTE), &oldProtect)) {printf("AdjustMemoryProtection failed. Error: %d\n", GetLastError());return FALSE;}// 保存原始字節SIZE_T bytesRead;if (!ReadProcessMemory(g_Cpdi.hProcess, g_pfWriteFile, &g_chOrgByte, sizeof(BYTE), &bytesRead)) {printf("ReadProcessMemory failed. Error: %d\n", GetLastError());VirtualProtectEx(g_Cpdi.hProcess, g_pfWriteFile, sizeof(BYTE), oldProtect, &oldProtect);return FALSE;}// 寫入INT3斷點SIZE_T bytesWritten;if (!WriteProcessMemory(g_Cpdi.hProcess, g_pfWriteFile, &g_chINT3, sizeof(BYTE), &bytesWritten)) {printf("WriteProcessMemory failed. Error: %d\n", GetLastError());VirtualProtectEx(g_Cpdi.hProcess, g_pfWriteFile, sizeof(BYTE), oldProtect, &oldProtect);return FALSE;}// 恢復原始內存保護VirtualProtectEx(g_Cpdi.hProcess, g_pfWriteFile, sizeof(BYTE), oldProtect, &oldProtect);// 刷新指令緩存if (!FlushInstructionCache(g_Cpdi.hProcess, g_pfWriteFile, sizeof(BYTE))) {printf("FlushInstructionCache failed. Error: %d\n", GetLastError());return FALSE;}g_bHooked = TRUE;// 使用%p格式輸出指針(自動適應64位/32位)printf("Hook installed at %p (original byte: 0x%02X)\n", g_pfWriteFile, g_chOrgByte);return TRUE;
}// 卸載鉤子
BOOL UninstallHook()
{if (!g_bHooked || !g_Cpdi.hProcess) return TRUE;DWORD oldProtect;// 調整內存保護if (!AdjustMemoryProtection(g_Cpdi.hProcess, g_pfWriteFile, sizeof(BYTE), &oldProtect)) {printf("AdjustMemoryProtection failed. Error: %d\n", GetLastError());return FALSE;}// 恢復原始字節SIZE_T bytesWritten;if (!WriteProcessMemory(g_Cpdi.hProcess, g_pfWriteFile, &g_chOrgByte, sizeof(BYTE), &bytesWritten)) {printf("WriteProcessMemory (restore) failed. Error: %d\n", GetLastError());VirtualProtectEx(g_Cpdi.hProcess, g_pfWriteFile, sizeof(BYTE), oldProtect, &oldProtect);return FALSE;}// 恢復原始內存保護VirtualProtectEx(g_Cpdi.hProcess, g_pfWriteFile, sizeof(BYTE), oldProtect, &oldProtect);// 刷新指令緩存if (!FlushInstructionCache(g_Cpdi.hProcess, g_pfWriteFile, sizeof(BYTE))) {printf("FlushInstructionCache failed. Error: %d\n", GetLastError());return FALSE;}g_bHooked = FALSE;printf("Hook uninstalled\n");return TRUE;
}// 處理單步異常
BOOL HandleSingleStepException(DWORD dwThreadId)
{// 重新設置斷點if (!g_pfWriteFile || !g_Cpdi.hProcess) return FALSE;// 打開線程句柄HANDLE hThread = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, FALSE, dwThreadId);if (!hThread) {printf("OpenThread failed. Error: %d\n", GetLastError());return FALSE;}// 清除單步標志CONTEXT ctx = {0};ctx.ContextFlags = CONTEXT_CONTROL;if (GetThreadContext(hThread, &ctx)) {ctx.EFlags &= ~0x100; // 清除TF標志if (!SetThreadContext(hThread, &ctx)) {printf("SetThreadContext failed. Error: %d\n", GetLastError());}} else {printf("GetThreadContext failed. Error: %d\n", GetLastError());}CloseHandle(hThread);// 重新安裝鉤子return InstallHook();
}// 調試循環實現
void DebugLoop()
{DEBUG_EVENT debugEvent = {0};DWORD dwContinueStatus = DBG_CONTINUE;while (WaitForDebugEvent(&debugEvent, INFINITE)) {dwContinueStatus = DBG_CONTINUE;switch (debugEvent.dwDebugEventCode) {case CREATE_PROCESS_DEBUG_EVENT:// 保存進程創建信息g_Cpdi = debugEvent.u.CreateProcessInfo;// 獲取WriteFile函數地址HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");if (hKernel32) {g_pfWriteFile = GetProcAddress(hKernel32, "WriteFile");if (g_pfWriteFile) {// 使用%p格式輸出指針printf("WriteFile address resolved: %p\n", g_pfWriteFile);} else {printf("Failed to find WriteFile address. Error: %d\n", GetLastError());}} else {printf("Failed to get kernel32 handle. Error: %d\n", GetLastError());}break;case EXCEPTION_DEBUG_EVENT:if (debugEvent.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) {// 首次斷點(系統斷點)if (g_bFirstBreakpoint) {g_bFirstBreakpoint = FALSE;printf("First breakpoint hit, installing hook...\n");if (!InstallHook()) {printf("Failed to install hook, terminating...\n");return;}} // 自定義斷點(WriteFile被調用)else if (g_pfWriteFile && (LPVOID)debugEvent.u.Exception.ExceptionRecord.ExceptionAddress == g_pfWriteFile) {printf("\n==== WriteFile intercepted! ====\n");printf("Thread ID: %d\n", debugEvent.dwThreadId);// 恢復原始字節if (!UninstallHook()) {printf("Failed to uninstall hook, continuing...\n");}// 設置單步執行HANDLE hThread = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, FALSE, debugEvent.dwThreadId);if (hThread) {CONTEXT ctx = {0};ctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;if (GetThreadContext(hThread, &ctx)) {// 使用Rip寄存器而非Eipctx.Rip = (DWORD64)g_pfWriteFile; // 設置RIP到WriteFile起始地址ctx.EFlags |= 0x100; // 設置TF標志(單步執行)if (!SetThreadContext(hThread, &ctx)) {printf("SetThreadContext failed. Error: %d\n", GetLastError());}} else {printf("GetThreadContext failed. Error: %d\n", GetLastError());}CloseHandle(hThread);} else {printf("OpenThread failed. Error: %d\n", GetLastError());}}}// 處理單步異常else if (debugEvent.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP) {printf("Single step exception handled\n");HandleSingleStepException(debugEvent.dwThreadId);}break;case EXIT_PROCESS_DEBUG_EVENT:// 目標進程退出printf("\nTarget process exited (Exit Code: %u)\n", debugEvent.u.ExitProcess.dwExitCode);UninstallHook();return;case UNLOAD_DLL_DEBUG_EVENT:// 如果kernel32被卸載,清理資源if (g_bHooked) {printf("Kernel32 unloaded, uninstalling hook\n");UninstallHook();}break;}// 繼續執行目標進程if (!ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, dwContinueStatus)) {printf("ContinueDebugEvent failed. Error: %d\n", GetLastError());return;}}
}
//gcc hookdbg64.c -o hookdbg64.exe -m64 -lkernel32 -luser32//hookdbg64.exe pid
4. 編譯
使用 GCC 編譯器執行以下命令生成可執行的調試器:
gcc hookdbg64.c -o hookdbg64.exe -m64 -lkernel32 -luser32
四、關鍵 API 函數解析
在實現過程中,有幾個關鍵的 API 函數需要重點關注:
1. DebugActiveProcess
該函數用于將當前調試進程附加到指定的目標進程。必須在具有調試權限的情況下調用。
2. ReadProcessMemory
和 WriteProcessMemory
這兩個函數分別用于讀取和寫入目標進程的內存,可以通過修改目標進程中的內存字節來安裝鉤子。
3. ContinueDebugEvent
此函數用于指示操作系統繼續執行調試目標進程。每次處理完調試事件后,必須調用此函數以防止目標程序掛起。
五、實戰調試步驟
- 首先確保
gmp.exe
正在運行。 - 在命令提示符下,執行
hookdbg64.exe <pid>
,將 PID 替換為目標進程的 ID。 - 觀察調試器輸出,確認是否正確安裝了鉤子并監控
WriteFile
的調用。
六、測試結果
通過上述步驟,將調試器連接到目標進程后,系統將正常監控到 WriteFile
的調用并生成相應的調試信息,如下所示:
Debugger attached to PID: <進程ID>
WriteFile address resolved: <地址>
結語
本文詳細介紹了如何使用 C實現動態程序調試及鉤子安裝,通過實例講解了操作系統的內存和調試機制。
如果你覺得本教程對你有幫助,請點贊??、收藏?、關注支持!歡迎在評論區留言交流技術細節!