EasyHook实战:C++ DLL注入与API Hook完整Demo解析

发布时间:2026/9/9 23:55:55

EasyHook实战:C++ DLL注入与API Hook完整Demo解析 简介面向C及Windows平台开发者的EasyHook函数钩子示例工程基于VS2010编译环境构建提供从DLL注入到API挂钩的完整稳定实现方案适用于文件访问监控、API调用追踪、程序行为分析等系统编程场景。包内合计三十六个文件包含Hook挂钩库与MFC注入程序两个核心模块源码涵盖头文件、实现文件、界面资源、工程配置等类型同时附带了编译好的动态库与静态导入库压缩包大小为278KB目录结构明确便于按模块学习与二次开发。目前已有两千零八十八位学习者浏览对想快速集成钩子功能、绕开EasyHook繁杂底层调用的开发者参考价值突出。实现上Hook库把挂钩流程封装为配置数组只需填写模块名、API函数名和新函数指针即可完成一条挂钩规则演示代码覆盖了CreateFileW、CreateFileA、ReadFile三个常用接口注入端则以MFC对话框接收目标进程ID并自动执行库注入免去手工编写注入逻辑。整体代码风格规范、注释到位是学习远程注入、API拦截与函数挂钩的实用范例也可直接移植为通用钩子模块。 先说结论如果你要在Windows平台上做API钩子又不想被授权费坑、不想自己面对一堆指令级裁缝的活EasyHook是目前最省心的选择之一。这篇博文整理的是一个我实际在用的完整Demo基于VS2010 C把一个“注入器 钩子DLL”的工程从头到尾讲清楚。工程可以直接拿来改适合刚接触函数钩子、想在老项目里快速加监控逻辑、或者被逆向需求逼着研究这个方向的朋友。其实早期我在这块也绕了不少弯路。真正开始用EasyHook是因为当时要给一个遗留系统加文件访问监控试了手写INLINE HOOK结果在x64下被指令重定位折磨了一周后来切到EasyHook两小时就通了。市面上讲EasyHook的资料不少但大多数都是托管版本C#的例子非托管C的完整Demo反而少。这篇就专门补这个缺口。1. 为什么偏偏是EasyHook选型背后的逻辑1.1 自己写钩子 vs 现成库函数钩子的本质是把目标函数开头的几条指令改写成一条跳转指令跳到我们自己的函数里执行。听起来简单但落地全是细节x86下短跳转占5字节长跳转占6字节x64下常用的是14字节的绝对跳转被覆盖掉的原指令必须先搬到一个“垫片”里执行原指令可能是相对寻址搬走之后地址就崩了必须做重定位如果正在有线程执行到一半你改了指令下一秒就是访问违规自己的函数执行完还得干净利落地回到原函数继续跑栈和寄存器一个都不能乱。这些细节自己处理就是著名的inline hook翻车现场。稍微有点规模的项目我都不建议自己造轮子。市面上成熟的库微软Detours是事实标准但3.0 Express版虽然免费商业授权要付费而且老版本在新系统上经常出兼容问题。EasyHook刚好补了这个空档——它开源、免费LGPL、C/C接口齐全在Windows 7到Windows 10上我都实际跑过稳定性没得挑。1.2 EasyHook的核心优势EasyHook和普通“CreateRemoteThread LoadLibrary”注入最大的区别是它把注入动作做成了驱动级操作。早期版本依赖内核驱动EasyHook32.sys / EasyHook64.sys来完成进程内的代码注入和指令改写所以抗反调试能力、注入成功率都明显比纯用户态方案高。对我们做工具链的人来说最直观的感受就一个字——稳。它内部把指令改写的活全封装好了你只需要关心三件事往哪个进程注入Hook哪个APIHook到之后干什么。另外EasyHook支持按线程做ACL过滤比如只想Hook主线程、排除某个线程都控制得明明白白。这个后来排查问题时帮了我大忙。Demo里我选了32位工程因为VS2010时代32位进程还是绝对主流64位操作逻辑完全一样选对DLL和编译选项就行。2. 工程搭建VS2010下的完整配置2.1 环境与补丁先交代环境。Demo是在VS2010 SP1上建的平台工具集默认v100Windows SDK用的7.0版本。建议先把SP1和KB983509补丁装上不然VS2010在Win10上经常有各种莫名崩溃和代码高亮卡死的小毛病和当前工程不一定直接相关但影响体验。如果你手头实在没有VS2010拿VS2013/2015/2019打开这个工程也基本无痛只需要把平台工具集改成v120/v140/v142Windows SDK选8.1或10即可。核心代码没有任何版本强绑定。需要准备的软件包EasyHook 2.7源码包包含inc、lib、release DLL我本地用的是2.7.7097VS2010 SP1一个方便看调试输出的工具比如DebugViewEasyHook包解压后目录里会有EasyHook32.dll、EasyHook64.dll、EasyHook32.lib、EasyHook64.lib以及一堆头文件。我们只需要把inc目录里的EasyHook.h系列头文件和对应lib引用到工程里。2.2 工程结构设计整个解决方案我拆成两个工程职责分开后面维护起来非常清爽工程AEasyHookDemo_InjectorWin32控制台程序x86作用是找到目标进程PID调用EasyHook的注入API把钩子DLL打进去并等一下用户命令来卸载。它要引用的库是EasyHook32.lib。工程BEasyHookDemo_DllWin32 DLLx86这是实际的业务工程里面做真正的Hook安装和日志逻辑。它也要引EasyHook32.lib。这两个工程的输出目录我建议统一指向同一个目录比如$(SolutionDir)bin\$(PlatformName)\$(Configuration)\这样跑Demo时DLL和EXE不会找不到彼此。这个细节初学的人很容易踩坑——明明编译成功了一运行提示“找不到DLL”。注意事项VS2010在编译DLL时默认可能不带导出定义。如果发现注入后在目标进程里加载DLL失败优先检查是不是DLL没有导出我们约定的入口函数。后面我会单独说导出方式。3. Demo核心代码解析一次完整注入与Hook3.1 注入器的实现Injector侧注入器的核心逻辑分三步找PID、注入、等待卸载。第一步用ToolHelp32遍历进程找到目标进程的PID。这个属于Windows基础API直接上代码#include windows.h #include tlhelp32.h #include wchar.h #include iostream #include EasyHook.h #pragma comment(lib, EasyHook32.lib) DWORD FindProcessId(const wchar_t* processName) { DWORD pid 0; HANDLE snapshot CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snapshot INVALID_HANDLE_VALUE) return 0; PROCESSENTRY32W pe { sizeof(PROCESSENTRY32W) }; if (Process32FirstW(snapshot, pe)) { do { if (_wcsicmp(pe.szExeFile, processName) 0) { pid pe.th32ProcessID; break; } } while (Process32NextW(snapshot, pe)); } CloseHandle(snapshot); return pid; }第二步拿到PID后调用RhInjectLibrary。这里有一个关键点DLL路径一定要用绝对路径。很多第一次跑的人直接传相对路径结果目标进程的工作目录和注入器完全不同注入当然失败。int wmain(int argc, wchar_t* argv[]) { if (argc 2) { wprintf(LUsage: EasyHookDemo_Injector ProcessName\n); return 1; } DWORD pid FindProcessId(argv[1]); if (pid 0) { wprintf(LProcess not found.\n); return 1; } wchar_t dllPath[MAX_PATH] { 0 }; GetFullPathNameW(LEasyHookDemo_Dll.dll, MAX_PATH, dllPath, NULL); // 第5个参数是被注入的DLL第4个参数传NULL表示原生DLL注入 NTSTATUS nt RhInjectLibrary( pid, 0, EASYHOOK_INJECT_DEFAULT, NULL, dllPath, NULL, 0 ); if (nt 0) { wprintf(LInject OK, pid %u\n, pid); } else { wprintf(LInject failed, NTSTATUS 0x%08X\n, nt); return 1; } // 等用户按回车再卸载 wprintf(LPress Enter to unhook and exit...\n); getchar(); RhUnhookLibrary(pid, dllPath); wprintf(LUnhooked.\n); return 0; }RhInjectLibrary的第一个参数是目标进程PID第二参数传0表示让EasyHook在目标进程里自己创建远程线程不需要我们指定线程ID。第三个参数EASYHOOK_INJECT_DEFAULT是默认注入方式具体走驱动还是用户态EasyHook内部会根据权限自动决策。最后一个参数0是透传数据大小我们用不到。3.2 钩子DLL入口函数与Hook安装钩子DLL真正的入口不是DllMain而是EasyHook约定的ExternalEntryPoint。这个函数由注入线程在目标进程里调用只有这个函数能拿到注入侧传过来的上下文。我们导出的方式用.def文件最稳在工程里加一个EasyHookDemo_Dll.defEXPORTS ExternalEntryPoint然后在cpp里实现这个导出函数#include windows.h #include strsafe.h #include EasyHook.h #pragma comment(lib, EasyHook32.lib) // 钩子句柄保存用于后续卸载 static TRACED_HOOK_HANDLE g_hHookCreateFileW NULL; extern C __declspec(dllexport) int __stdcall ExternalEntryPoint( void* InParam, DWORD InParamSize, void* InContext) { // 安装CreateFileW钩子 NTSTATUS nt LhInstallHook( CreateFileW, MyCreateFileW, NULL, g_hHookCreateFileW); if (nt 0) { // 安装失败返回负值给注入器 return (int)nt; } // 让钩子作用于目标进程的所有线程 LhSetExclusiveACL(0, 0, g_hHookCreateFileW); return 0; }这一步就是整个Demo的心脏。LhInstallHook的参数含义如下CreateFileW我们要Hook的API地址MyCreateFileW我们的替代函数地址第三个参数是回调事务型监控时用我们直接传NULL第四个参数是输出句柄供卸载/查询用。安装完成后我还调了LhSetExclusiveACL(0, 0, ...)。这里第一次接触的人容易发懵——传0,0是“排除零个进程、零个线程”的意思也就是不排除任何东西等于所有线程都生效。反过来想设置成“只有当前线程”时需要用LhSetInclusiveACL它表达“只包含谁”。这个语义我经常搞混写了注释防止以后忘。3.3 被Hook函数的完整写法MyCreateFileW是真正干活的函数。它必须和原始CreateFileW完全一致的签名和调用约定否则栈就崩了。函数内部先记录日志然后调用被保存的原始函数完成真实的文件打开动作typedef HANDLE (WINAPI *PFN_CreateFileW)( LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); HANDLE WINAPI MyCreateFileW( LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { // 用OutputDebugString输出避免在Hook里再调用文件API导致递归 wchar_t msg[512] { 0 }; StringCchPrintfW(msg, 512, L[EasyHookDemo] CreateFileW: %s\n, lpFileName); OutputDebugStringW(msg); // 获得原始函数地址然后调用 PFN_CreateFileW pOriginal (PFN_CreateFileW)LhGetHookProc(g_hHookCreateFileW); if (pOriginal NULL) return INVALID_HANDLE_VALUE; return pOriginal(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); }特别注意递归问题。刚开始试这个Demo时我犯过一个低级错误直接在Hook函数里用fopen写日志文件。而fopen内部走的同样是CreateFileW那条链路于是命中自己的Hook又去写日志又命中Hook……无限递归直接把目标进程栈干爆。调试了一下午才定位。日志输出优先选OutputDebugStringW或者提前把日志文件句柄打开不走CreateFileW这个API。LhGetHookProc的作用是从钩子句柄里拿到“原始函数指针”。EasyHook在改写完指令后会把原始函数的前几条指令完整搬移到一个私有区域并把那条私有区域的地址作为原始函数返回给我们。我们自己写inline hook时最难搞的trampoline部分在这就是一个API调用。3.4 DllMain该做什么DllMain里原则上什么都不用做。EasyHook的注入流程不依赖我们在DllMain里干额外的事保持最简反而更安全BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { return TRUE; }如果你的Hook逻辑需要初始化一些全局状态可以在ExternalEntryPoint里做。DllMain在Loader Lock限制下容易出幺蛾子这在所有DLL编程里都是常识。4. 稳定性的关键踩坑实录与排查技巧4.1 编译/运行时最常踩的四个坑坑一X86/X64架构不匹配注入器的编译目标、钩子DLL的编译目标、目标进程的架构三者必须一致。32位注入器 64位目标进程或者反过来错误码清一色是0xC000007BSTATUS_INVALID_IMAGE_FORMAT。排查第一步就是统一架构。坑二DLL路径不对上面代码里我用了GetFullPathNameW把相对路径转成绝对路径。经验是永远不要在注入相关代码里写裸的相对路径。目标进程的工作目录和你想象中完全不是一回事尤其用计划任务、服务方式启动的目标进程。坑三Hook函数签名不一致Windows API分WINAPI即__stdcall和__cdecl两种调用约定参数个数和返回值都得和原函数一模一样。一个最典型的故障就是Hook MessageBoxA时把参数写成了const char*结果原函数传的是const wchar_t*栈上取出来的内容全是乱码甚至直接访问违规。建议Hook任何API前先去头文件里把函数原型完整复制过来再改成自己的函数名。坑四EasyHook32.dll没有被正确加载EasyHook落地执行时不仅目标进程需要能加载你的钩子DLL还需要能加载EasyHook32.dll这个运行时库。我把三个文件都放到同一个输出目录里就再没遇到过“加载DLL失败”的报错。4.2 常见错误码速查NTSTATUS / 现象含义处理方式0xC000007B映像格式错误架构不匹配统一x86/x640xC0000005访问违规Hook函数原型写错核对API签名和调用约定注入函数返回负数LhInstallHook失败检查是否重复安装同一条Hook目标进程无日志输出DLL路径错误 / DllMain异常用Process Explorer确认DLL是否被加载用DebugView看调试输出目标进程直接被终止Hook函数递归 / 栈损坏检查日志IO是否重入了原API排查工具我固定用三件套Process Explorer看DLL有没有被目标进程加载、DebugView看OutputDebugString输出、VS的Attach to Process给目标进程上调试器直接在Hook函数里下断点。5. 从Demo到生产扩展方向5.1 改成按线程控制钩子LhSetExclusiveACL(0, 0, ...)是“所有线程都生效”。如果想只在特定线程生效比如只想Hook某个工作线程可以用LhSetInclusiveACL// 只对当前线程生效 LhSetInclusiveACL(1, 1, g_hHookCreateFileW);第二个参数是线程ID列表这里可以传GetCurrentThreadId()。这种细粒度控制在分析某些多线程程序时可以避免误伤。5.2 在Hook函数里做更复杂的事OutputDebugStringW只适合轻量验证。生产场景下你大概率需要把日志写到文件或者上报到服务端。这时有两个选择一是提前打开一个日志文件句柄在Hook函数里直接写文件不走系统API二是用共享内存或命名管道把数据交给外部进程处理。两个方案我都用过简单数据量直接用文件句柄方案最稳数据量大、需要格式化聚合时再上管道。5.3 换成新版Visual StudioVS2010是历史产物如果你只是想在VS2019/2022上用同样的思路操作很简单用Visual Studio Installer把“使用C的桌面开发”装上加载工程后右键重定目标平台工具集选v142或v143EasyHook的lib换一下即可。注意新版VS默认编译C17EasyHook老代码理论上能直接过唯一要改的是个别宏定义位置。EasyHook这套“驱动注用户DLL 指令改写”的架构即使放到现在也并不过时不少商业软件的理念和它一模一样。Demo本身不大但里面藏着的工程经验——绝对路径、ACL语义、递归防护、架构匹配——是我实际跑了很多遍才摸透的也是真正的稳定关键。先按原样跑通再改业务你会少走一半弯路。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/9/9 23:55:55

Maven 3.9.6升级实战:配置优化、踩坑记录与插件兼容指南

作为一个常年跟 Java 项目、CI 流水线、私有仓库打交道的人,我对 Maven 的感情一直很复杂。一方面它稳定、可靠,是 Java 生态的基石之一;另一方面,它偶尔冒出来的诡异报错,也实打实地让人头疼。这次把环境从 3.6.3 和 …

2026/9/9 23:55:55

mknod命令详解:手动创建设备节点的原理与实战

说实话,很多用 Linux 用了几年的朋友,天天跟 /dev/null 、 /dev/ttyS0 打交道,但要问一句"这些设备文件到底是怎么来的"、"能不能自己手动创建一个设备节点",多半会愣一下。 mknod 这个命令平时用得少&…

2026/9/10 0:51:02

CoppeliaSim + Python实现差速小车目标追踪:从环境搭建到PID调参

简介:Vrep/CoppeliaSim与Python结合的小车目标点追踪项目,面向机器人仿真学习者和需要快速上手的开发者,提供了一套可直接运行的仿真控制示例。压缩包共17个文件,大小343KB,涵盖Python控制脚本、CoppeliaSim场景文件、…

2026/9/10 0:51:02

Java单元测试利器Mockito:核心玩法、实战套路与避坑指南

我做了这么多年Java后端,Mockito这个库几乎每天都在用。它是Java生态里最流行的单元测试mock框架,解决的问题很直白:让你在测试里替掉那些不好构造、不稳定、或者压根还没实现的依赖对象。比如Service层要查数据库、调第三方HTTP接口、发MQ消…

2026/9/10 0:51:02

LCD1602与C51单片机驱动实战:从时序原理到排错技巧

简介:这是一套面向C51单片机学习者与电子爱好者的LCD1602显示例程合集。例程围绕矩阵按键键值显示、DS18B20温度读取、DS1302时钟时间显示以及ADC0832电压转换这四类典型应用,演示了如何通过单片机控制字符型液晶屏将数据直观呈现,可直接借鉴…

2026/9/10 0:51:02

CTS测试AaptParser failed报错全解析:APK解析失败根源与修复

做CTS测试的人,应该都见过这类让人血压飙升的报错: AaptParser failed for file CtsCameraTestCases.apk. The APK wont be installed 。 这句话翻译成人话就是:测试框架尝试解析CtsCameraTestCases这个APK的时候失败了,所以这…

2026/9/9 13:11:35

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/8 7:15:15

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/10 0:00:55

目录对比去重实战:用哈希算法精准清理重复文件

我电脑里现在还有一块换了三次机的“数据墓地”硬盘,里面存着2016年以前所有旧笔记本的完整备份。平时不觉得有什么,直到前阵子想把它整理归档,发现同一个安装包、同一批照片、同一份论文草稿,在几个不同的备份目录里反复出现。更…

2026/9/10 0:00:55

Leaflet离线地图完整Demo合集:内网部署与坐标纠偏实战

简介:这是一份面向Web GIS开发者的LeafLet离线地图示例合集,帮助开发者快速掌握离线地图从搭建到交互的完整流程。压缩包共723个文件,大小14.06MB,以319个js脚本、175个html页面和29个css样式文件为主体,配合png/svg图…

2026/9/10 0:00:55

MATLAB读取Rinex 3.02观测文件:多系统GNSS数据解析实战

简介:基于MATLAB开发的Rinex3.02版观测文件(o文件)读取代码包,面向卫星定位导航方向的学习者与研究人员,用于解决新版观测文件的数据解析、历元提取与时间转换问题。压缩包共4个文件,包含两个m脚本、一个19…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/9 10:21:54

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码