[逆向工程] C實現過程調試與鉤子安裝(二十七)

[逆向工程] 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. ReadProcessMemoryWriteProcessMemory

這兩個函數分別用于讀取和寫入目標進程的內存,可以通過修改目標進程中的內存字節來安裝鉤子。

3. ContinueDebugEvent

此函數用于指示操作系統繼續執行調試目標進程。每次處理完調試事件后,必須調用此函數以防止目標程序掛起。

五、實戰調試步驟

  1. 首先確保 gmp.exe 正在運行。
  2. 在命令提示符下,執行 hookdbg64.exe <pid>,將 PID 替換為目標進程的 ID。
  3. 觀察調試器輸出,確認是否正確安裝了鉤子并監控 WriteFile 的調用。

六、測試結果

通過上述步驟,將調試器連接到目標進程后,系統將正常監控到 WriteFile 的調用并生成相應的調試信息,如下所示:

Debugger attached to PID: <進程ID>
WriteFile address resolved: <地址>

結語

本文詳細介紹了如何使用 C實現動態程序調試及鉤子安裝,通過實例講解了操作系統的內存和調試機制。

如果你覺得本教程對你有幫助,請點贊??、收藏?、關注支持!歡迎在評論區留言交流技術細節!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/86137.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/86137.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/86137.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

分頁查詢的實現

第一步&#xff1a;導入pom依賴 <!--配置PageHelper分頁插件--><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.6</version><exclusions>…

JDK17 Http Request 異步處理 源碼刨析

為什么可以異步&#xff1f; #調用起始源碼 // 3. 發送異步請求并處理響應 CompletableFuture future client.sendAsync( request, HttpResponse.BodyHandlers.ofString() // 響應體轉為字符串 ).thenApply(response -> { // 狀態碼檢查&#xff08;非200系列拋出異常&…

會計 - 合并4 - 或有對價的會計處理

一、多次交易(構成一攬子交易)形成非同一控制下企業合并 構成一攬子交易的,在取得控制權時確認長期股權投資;取得控制權之前已支付的款項應作為預付投資款項(通常以”預付賬款“科目核算)處理。 滿足以下一種或多種情況的,通常應將多次交易事項作為“一攬子交易”進行會…

【HTTP三個基礎問題】

面試官您好&#xff01;HTTP是超文本傳輸協議&#xff0c;是互聯網上客戶端和服務器之間傳輸超文本數據&#xff08;比如文字、圖片、音頻、視頻等&#xff09;的核心協議&#xff0c;當前互聯網應用最廣泛的版本是HTTP1.1&#xff0c;它基于經典的C/S模型&#xff0c;也就是客…

NLP中的input_ids是什么?

在自然語言處理(NLP)中,input_ids 是什么 在自然語言處理(NLP)中,input_ids 是將文本轉換為模型可處理的數字表示后的結果,是模型輸入的核心參數之一。 一、基本概念 文本數字化 原始文本(如 “Hello world!”)無法直接被模型處理,需要通過分詞器(Tokenizer) 將其…

?? Linux Docker 基本命令參數詳解

&#x1f433; Linux Docker 基本命令參數詳解 &#x1f4d8; 1. Docker 簡介 Docker 是一個開源的容器化平臺&#xff0c;它通過將應用及其依賴打包到一個輕量級、可移植的容器中&#xff0c;從而實現跨平臺運行。Docker 采用 C/S 架構&#xff0c;服務端稱為 Docker Daemon&a…

Spring IoC 模塊設計文檔

注&#xff1a;碼友們&#xff0c;我們是從設計的角度一步步學習和分解Spring&#xff1b;所以不要一上來就想看源碼&#xff0c;也不需要關心Spring具體加載進去的&#xff1b;我們只封裝工具&#xff08;如IoC&#xff09;&#xff0c;至于調用&#xff0c;暫時不用考慮&…

Linux(生產消費者模型/線程池)

目錄 一 生產消費者模型 1. 概念&#xff1a; 2. 基于阻塞隊列的生產消費者模型&#xff1a; 1. 對鎖封裝 2. 對條件變量封裝 二 信號量(posix) 1. 概念 2. API 3. 基于環形隊列的生產消費者模型 三 線程池 1. 概念 2. 示例 四 補充字段 1. 可重入函數 VS 線程安…

無線網絡掃描與分析工具 LizardSystems Wi-Fi Scanner 25.05

—————【下 載 地 址】——————— 【?本章下載一】&#xff1a;https://pan.xunlei.com/s/VOS4QQ9APt3FgFQcxyArBiZlA1?pwdi4du# 【?本章下載二】&#xff1a;https://pan.xunlei.com/s/VOS4QQ9APt3FgFQcxyArBiZlA1?pwdi4du# 【百款黑科技】&#xff1a;https://uc…

Java Map完全指南:從基礎到高級應用

文章目錄 1. Map接口概述Map的基本特性 2. Map接口的核心方法基本操作方法批量操作方法 3. 主要實現類詳解3.1 HashMap3.2 LinkedHashMap3.3 TreeMap3.4 ConcurrentHashMap 4. 高級特性和方法4.1 JDK 1.8新增方法4.2 Stream API結合使用 5. 性能比較和選擇建議性能對比表選擇建…

[最全總結]城市災害應急管理系統

城市災害應急管理集成系統 | 國家重點研發政府間合作項目 Vue+ElementUI+Bpmn+Cesium+Java SpringBoot 項目描述 在智慧城市戰略背景下,項目面向內澇、團霧和火災等災害,開發了集災害模型集成模擬、場景可視化與應急預案管理于一體的系統,系統各子模塊進行軟件功能測試,測…

QtWidgets模塊功能及架構解析

QtWidgets 是 Qt 框架中用于創建傳統桌面應用程序圖形用戶界面(GUI)的核心模塊。在 Qt 6.0 中&#xff0c;QtWidgets 模塊繼續提供豐富的 UI 組件和功能&#xff0c;盡管 Qt 正在向 QML 方向演進&#xff0c;但 QtWidgets 仍然是許多桌面應用程序的基礎。 一、主要功能 基礎窗…

grep、wc 與管道符快速上手指南

&#x1f3af; Linux grep、wc 與管道符快速上手指南&#xff1a;從入門到實用 &#x1f4c5; 更新時間&#xff1a;2025年6月7日 &#x1f3f7;? 標簽&#xff1a;Linux | grep | wc | 管道符 | 命令行 文章目錄 前言&#x1f31f; 一、grep、wc 和管道符簡介1.核心功能2.核心…

C++11 右值引用:從入門到精通

文章目錄 一、引言二、左值和右值&#xff08;一&#xff09;概念&#xff08;二&#xff09;區別和判斷方法 三、左值引用和右值引用&#xff08;一&#xff09;左值引用&#xff08;二&#xff09;右值引用 四、移動語義&#xff08;一&#xff09;概念和必要性&#xff08;二…

java復習 04

心情復雜呢&#xff0c;現在是6.7高考第一天&#xff0c;那年今日此時此刻我還在考場掙扎數學&#xff0c;雖然結果的確很糟糕&#xff0c;&#xff0c;現在我有點對自己生氣明明很多事情待辦確無所事事沒有目標&#xff0c;不要忘記曾經的自己是什么樣子的&#xff0c;去年今日…

從零開始搭建 Pytest 測試框架(Python 3.8 + PyCharm 版)

概述 在軟件開發中&#xff0c;自動化測試是確保代碼質量的重要方式。而 Pytest 是一個功能強大且易于上手的 Python 測試框架&#xff0c;非常適合初學者入門。 本文將帶你一步步完成&#xff1a; 安裝和配置 Pytest在 PyCharm 中搭建一個清晰的測試項目結構 準備工作 在…

用電腦通過網口控制keysight示波器

KEYSIGHT示波器HD304MSO性能 亮點: 體驗 200 MHz 至 1 GHz 的帶寬和 4 個模擬通道。與 12 位 ADC 相比,使用 14 位模數轉換器 (ADC) 將垂直分辨率提高四倍。使用 10.1 英寸電容式觸摸屏輕松查看和分析您的信號。捕獲 50 μVRMS 本底噪聲的較小信號。使用獨有區域觸摸在幾秒…

Java Smart 系統題庫試卷管理模塊設計:從需求到開發的實戰指南

在教育信息化不斷推進的背景下&#xff0c;高效的題庫及試卷管理系統至關重要。Java Smart 系統中的題庫及試卷管理模塊&#xff0c;旨在為教師提供便捷的試題錄入、試卷生成與管理功能&#xff0c;同時方便學生在線練習與考試。本文將詳細介紹該模塊的設計思路與核心代碼實現。…

PDF圖片和表格等信息提取開源項目

文章目錄 綜合性工具專門的表格提取工具經典工具 綜合性工具 PDF-Extract-Kit - opendatalab開發的綜合工具包&#xff0c;包含布局檢測、公式檢測、公式識別和OCR功能 倉庫&#xff1a;opendatalab/PDF-Extract-Kit特點&#xff1a;功能全面&#xff0c;包含表格內容提取的S…

git小烏龜不顯示圖標狀態解決方案

第一步 在開始菜單的搜索處&#xff0c;輸入regedit命令&#xff0c;打開注冊表。 第二步 在注冊表編輯器中&#xff0c;找到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers 這一項。 第三步 讓Tortoise相關的項目排在前…