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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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相关的项目排在前…