尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Windows用户态API Hook实战:拦截send/recv网络调用
简介这是一份面向Windows逆向工程师、网络安全研究人员及系统级开发者的API Hook实战工具包聚焦于拦截指定进程的网络收发行为解决进程级网络通信监控与调试难题。资源共22个文件包含8个头文件h与4个源码文件cpp构成核心Hook逻辑2个DLL工程配置dsp/dsw支持VC6编译另有2个关键说明文本txt详解进程枚举与内存补丁技术如修改API前8字节跳转至VirtualQuery/VirtualProtect/WriteProcessMemory辅以DLL、ICO、DEF等配套资源整体仅37KB轻量但功能完整。已有874人学习下载适合中高级开发者深入理解用户态API Hook原理与实现细节。读者可直接复用其进程选择机制、内存保护绕过策略及网络数据包拦截框架快速构建定制化监控工具同时掌握ULHook、ShareMemory等模块在跨进程通信中的协同设计思路。1. 用 API HOOK 拦截指定进程的网络收发——不是抓包而是“劫持”系统调用入口你不需要 Wireshark也不依赖 NDIS 或 WFP 驱动当你想在用户态精准控制某个目标进程比如一个未提供调试接口的第三方客户端的 HTTP 请求头、篡改 POST body、或记录其所有 socket 发送/接收的原始字节流时API HOOK 是最直接、最轻量、也最容易落地的方案。它不修改内核、不依赖管理员权限、不触发杀软驱动级告警核心逻辑是定位目标进程中send/recv/WSASend/WSARecv等关键函数在内存中的真实地址用自定义函数替换其入口跳转从而在每次调用发生前/后插入你的逻辑。标题中.zip文件名暗示这是一个可编译运行的 VC 工程而非纯理论描述——这意味着它必然涉及VirtualQuery定位模块内存布局、VirtualProtect修改页保护属性、以及对 IAT导入地址表或 inline hook 的具体实现选择。本文面向有 Win32 编程基础的开发者重点讲清为什么选send/recv而非NtWriteFileIAT Hook 和 Inline Hook 各在哪种场景下更稳如何避免被目标进程反调试机制识别以及最关键的——怎样确保 hook 只作用于指定 PID 进程而不污染整个系统2. 从 VirtualQuery 定位模块起始地址到 VirtualProtect 解锁代码页Hook 前的内存准备API HOOK 的前提是让目标进程的内存可写。而 Windows 默认将代码段设为PAGE_EXECUTE_READ直接覆写会触发访问违例。因此必须先用VirtualQuery获取目标函数所在内存页的属性再用VirtualProtect临时提升为PAGE_EXECUTE_READWRITE。这一步看似简单但实际落地时极易出错——尤其当目标函数位于系统 DLL如ws2_32.dll中且该 DLL 被多个进程共享时VirtualProtect的作用域仅限于当前进程不会影响其他进程这是安全的但若误操作了自身进程的kernel32.dll地址则可能引发崩溃。2.1 用 VirtualQuery 精确获取 send 函数所在内存页边界VirtualQuery不是查函数地址而是查地址所属内存页的元信息。以send函数为例需先通过GetModuleHandle获取ws2_32.dll模块基址再用GetProcAddress得到send的 RVA相对虚拟地址最终计算出绝对地址HMODULE hWs2 GetModuleHandle(Lws2_32.dll); if (!hWs2) return FALSE; FARPROC pSend GetProcAddress(hWs2, send); if (!pSend) return FALSE; MEMORY_BASIC_INFORMATION mbi {0}; SIZE_T result VirtualQuery(pSend, mbi, sizeof(mbi)); if (result 0) return FALSE; // mbi.BaseAddress 是该页起始地址mbi.RegionSize 是页大小通常 4KB // 注意pSend 必须落在 [mbi.BaseAddress, mbi.BaseAddress mbi.RegionSize) 区间内提示VirtualQuery返回的mbi.Protect字段值为PAGE_EXECUTE_READ说明该页不可写。若此处返回PAGE_NOACCESS或PAGE_GUARD说明函数可能被 ASLR 随机化后映射到了受保护区域需检查是否加载了正确版本的ws2_32.dll例如 x64 进程不能用 x86 DLL。2.2 用 VirtualProtect 临时解除写保护并验证页属性变更VirtualProtect的第三个参数flNewProtect必须设为PAGE_EXECUTE_READWRITE且第四个参数lpflOldProtect必须传入有效指针否则调用失败DWORD oldProtect 0; BOOL bRet VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, oldProtect); if (!bRet) { DWORD err GetLastError(); // 常见错误ERROR_ACCESS_DENIED权限不足或 ERROR_INVALID_PARAMETER地址非法 return FALSE; } // 此时可安全写入机器码如 jmp 指令到 mbi.BaseAddress 开始的内存2.2.1 关键参数说明与常见陷阱参数含义典型取值注意事项lpAddress起始地址mbi.BaseAddress必须是对齐到页边界的地址否则VirtualProtect失败dwSize修改范围mbi.RegionSize即使只改前 5 字节也必须传整页大小否则部分区域仍受保护flNewProtect新保护属性PAGE_EXECUTE_READWRITE绝不能用PAGE_READWRITE否则后续执行跳转会因无执行权限崩溃lpflOldProtect存储旧属性oldProtect必须是非 NULL 指针用于恢复时还原注意VirtualProtect成功后必须在 hook 完成后立即用相同参数恢复原保护属性否则目标进程后续调用该函数时可能因内存状态异常而崩溃。恢复代码应放在 hook 函数注册完毕后的紧邻位置且需保证异常安全建议用 RAII 封装或__try/__finally。2.3 为什么必须结合 VirtualQuery VirtualProtect单用一个不行仅用VirtualProtect直接传pSend地址因pSend通常不是页首地址VirtualProtect会失败ERROR_INVALID_PARAMETER仅用VirtualQuery只能读取属性无法修改hook 无从下手绕过二者直接WriteProcessMemory对远程进程无效本方案针对当前进程自身 hook且即使对远程进程也需先VirtualProtectEx解锁目标页。因此VirtualQuery → VirtualProtect → 写入跳转指令 → VirtualProtect恢复是不可省略的标准三步链。3. IAT Hook 与 Inline Hook 的选型对比哪种更适合拦截 send/recvHook 方法决定稳定性、兼容性与适用范围。标题中未明确实现方式但根据 VC 工程常见实践及VirtualQuery/VirtualProtect的出现频次大概率采用Inline Hook直接修改函数入口机器码而非 IAT Hook修改导入表。二者本质区别在于作用层级IAT Hook 只能拦截通过导入表调用的 API而 Inline Hook 可拦截所有调用路径包括直接 call、间接 call、甚至内联展开。3.1 IAT Hook原理、代码与局限性IAT Hook 修改目标模块的导入地址表Import Address Table将send在 IAT 中的地址指向你的代理函数。适用于已知调用方模块如 hook 自己的 exe但对系统 DLL 中的send调用无效因其调用不经过 IAT。// 示例hook 当前模块对 ws2_32.dll 的 send 调用 PIMAGE_DOS_HEADER dosHeader (PIMAGE_DOS_HEADER)GetModuleHandle(NULL); PIMAGE_NT_HEADERS ntHeaders (PIMAGE_NT_HEADERS)((BYTE*)dosHeader dosHeader-e_lfanew); PIMAGE_IMPORT_DESCRIPTOR importDesc (PIMAGE_IMPORT_DESCRIPTOR) ((BYTE*)dosHeader ntHeaders-OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); for (; importDesc-Name; importDesc) { LPCSTR dllName (LPCSTR)((BYTE*)dosHeader importDesc-Name); if (lstrcmpiA(dllName, ws2_32.dll) 0) { PIMAGE_THUNK_DATA firstThunk (PIMAGE_THUNK_DATA) ((BYTE*)dosHeader importDesc-FirstThunk); for (int i 0; firstThunk[i].u1.Function; i) { FARPROC* funcAddr (FARPROC*)firstThunk[i].u1.Function; if (GetProcAddress(GetModuleHandleA(ws2_32.dll), send) *funcAddr) { // 修改 IAT 条目 DWORD oldProtect; VirtualProtect(funcAddr, sizeof(FARPROC), PAGE_READWRITE, oldProtect); *funcAddr (FARPROC)MySendHook; VirtualProtect(funcAddr, sizeof(FARPROC), oldProtect, oldProtect); break; } } } }3.1.1 IAT Hook 的三大硬伤无法拦截动态链接调用若目标进程用LoadLibraryGetProcAddress获取send地址并缓存IAT Hook 完全失效对系统 DLL 调用无效ws2_32.dll内部调用send时走的是内部符号解析不查 IAT多线程竞争风险IAT 条目被多线程同时读写需加锁而锁本身可能引入性能瓶颈。3.2 Inline Hook修改函数入口指令真正“劫持”每一次调用Inline Hook 直接覆写send函数开头几字节x64 下通常 14 字节mov r10, rcx; mov eax, 0xXXXX; jmp [rip0]插入jmp MySendHook指令。所有对该函数的调用无论来源都会跳转到你的代理函数。// x64 下典型的 jmp rel32 指令5 字节0xE9 4 字节偏移 BYTE jmpCode[5] {0xE9, 0x00, 0x00, 0x00, 0x00}; DWORD64 targetAddr (DWORD64)MySendHook; DWORD64 currentAddr (DWORD64)pSend; *(DWORD*)(jmpCode 1) (DWORD)(targetAddr - currentAddr - 5); // rel32 target - current - 5 // 执行 VirtualProtect 后写入 memcpy(pSend, jmpCode, 5);3.2.1 Inline Hook 的关键保障措施指令长度对齐必须覆盖完整指令边界。send函数开头通常是push rbp/mov rbp, rsp等需反汇编确认前 5 字节是否为独立指令可用DbgHelp的SymFromAddrDisasm辅助跳转目标函数需保存寄存器MySendHook开头必须push rax/rbx/rcx/rdx/r8-r11x64 调用约定要求 caller 保存 r12-r15callee 保存 rbp/rsp/rsi/rdi/r12-r15否则目标进程原有逻辑崩溃原函数跳转桩TrampolineMySendHook处理完后需跳回send原始代码的第 6 字节处即跳过被覆写的 5 字节此跳转桩需单独分配可执行内存VirtualAlloc(..., PAGE_EXECUTE_READWRITE)并写入原始 5 字节 jmp指令。提示VC 工程中常将 trampoline 分配在.data段或堆上但必须确保该内存页具有PAGE_EXECUTE属性否则jmp会触发STATUS_ACCESS_VIOLATION。4. 精准作用于指定进程PID 过滤与多进程环境下的 Hook 隔离策略标题强调“指定进程”意味着不能全局 hook 所有send调用否则会干扰系统或其他应用。常见误区是在MySendHook中简单判断GetCurrentProcessId() targetPid——这完全错误因为MySendHook运行在目标进程上下文中GetCurrentProcessId()返回的就是目标 PID永远为真。正确做法必须在hook 注入阶段就限定作用域。4.1 进程注入时机决定作用域DLL 注入 vs. 直接编译进目标进程若工程是独立 EXE如HookInjector.exe则需通过CreateRemoteThreadLoadLibrary将 hook DLL 注入目标 PID 进程此时 hook 仅存在于该进程地址空间天然隔离若工程是静态链接的 LIB 或直接编译进目标进程源码则需在DllMain或初始化函数中判断GetCurrentProcessId()是否等于预设targetPid仅在此时执行VirtualProtectmemcpy。// DllMain 中的 PID 过滤推荐 BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: if (GetCurrentProcessId() g_TargetPid) { // g_TargetPid 由外部传入或配置文件读取 InstallSendHook(); // 执行 VirtualQuery/VirtualProtect/memcpy } break; case DLL_PROCESS_DETACH: if (GetCurrentProcessId() g_TargetPid) { UninstallSendHook(); // 恢复原指令 } break; } return TRUE; }4.2 多线程安全Hook 安装/卸载必须串行化send/recv是高频调用函数若在多线程环境下安装 hook 时某线程正执行到被覆写区域会导致非法指令异常。解决方案使用原子标志 自旋等待设置全局volatile BOOL g_HookInstalling TRUEMySendHook开头检测此标志若为真则Sleep(1)并重试直到安装完成暂停目标进程所有线程调用SuspendThread遍历EnumThreads获取所有线程句柄安装完再ResumeThread但开销大且可能被反调试检测采用“热补丁”式原子替换先在目标页外准备好新指令再用单条mov指令x64 下mov qword ptr [rax], rbx原子替换跳转地址但需硬件支持cmpxchg16b且复杂度高。推荐第一种在InstallSendHook中置g_HookInstalling TRUE在MySendHook开头添加while (g_HookInstalling) { Sleep(0); // 让出时间片 }4.3 如何获取目标进程的 PID三种可靠方式方式代码示意适用场景注意事项命令行参数int pid _wtoi(argv[1]);启动时指定需目标进程已运行且 PID 可知进程名枚举CreateToolhelp32SnapshotProcess32First/Next通用需遍历所有进程注意IsWow64Process判断架构窗口句柄反查GetWindowThreadProcessId(hwnd, pid)有 GUI 的目标需提前获知窗口类名或标题// 通过进程名获取 PID兼容 x64/x86 DWORD GetPidByName(LPCWSTR procName) { HANDLE hSnap CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 pe32 { sizeof(pe32) }; DWORD pid 0; if (Process32First(hSnap, pe32)) { do { if (_wcsicmp(pe32.szExeFile, procName) 0) { pid pe32.th32ProcessID; break; } } while (Process32Next(hSnap, pe32)); } CloseHandle(hSnap); return pid; }5. 实战验证捕获数据包内容、过滤条件设置与常见崩溃点排查Hook 成功后真正的挑战才开始如何从MySendHook中提取有意义的数据如何避免因处理不当导致目标进程卡死以下给出可直接复用的验证逻辑与排错清单。5.1 MySendHook 的标准结构与数据提取逻辑// x64 调用约定rcxSOCKET, rdxbuf, r8len, r9flags __declspec(naked) int __stdcall MySendHook(SOCKET s, const char* buf, int len, int flags) { // 1. 保存寄存器callee-saved __asm { push rax push rbx push rcx push rdx push rsi push rdi push r8 push r9 push r10 push r11 push r12 push r13 push r14 push r15 push rbp } // 2. 调用原始 send 前记录数据 if (g_HookEnabled GetCurrentProcessId() g_TargetPid) { // 关键深拷贝 buf因目标进程可能复用同一缓冲区 char* copyBuf (char*)malloc(len); if (copyBuf) { memcpy(copyBuf, buf, len); // 记录到文件或队列fprintf(g_LogFile, [SEND %d] %.*s\n, len, len, copyBuf); free(copyBuf); } } // 3. 跳转到原始 send 的 trampoline __asm { pop rbp pop r15 pop r14 pop r13 pop r12 pop r11 pop r10 pop r9 pop r8 pop rdi pop rsi pop rdx pop rcx pop rbx pop rax jmp g_SendTrampoline // 5 字节原始指令 jmp 回 send5 } }5.1.1 数据捕获的三个必检项检查点问题表现解决方案缓冲区生命周期日志中出现乱码或空数据buf指针在send返回后可能被目标进程释放必须mallocmemcpy立即复制Unicode/UTF-8 混淆HTTP 请求头显示为乱码send传输的是原始字节无需解码若需解析用MultiByteToWideChar(CP_UTF8, ...)转换大包截断只捕获前 1024 字节len参数即实际发送长度send可能分多次调用需按len完整记录勿假设为文本5.2 五类高频崩溃原因与对应日志线索崩溃现象日志/调试器线索根本原因修复动作Access Violation at 0xXXXXXXXX异常地址接近send地址VirtualProtect未成功或恢复时写入了错误字节检查VirtualProtect返回值用ReadProcessMemory验证写入内容目标进程启动即退出事件查看器报Application Error 0xc0000005hook 覆盖了非指令边界或 trampoline 跳转地址错误用dumpbin /disasm ws2_32.dll确认send开头指令长度Log 文件为空fopen成功但fwrite无输出g_HookEnabled未置 TRUE或 PID 判断始终为 FALSE在DllMain中OutputDebugString打印 PID 对比CPU 占用 100%MySendHook中死循环g_HookInstalling未置 FALSE或Sleep(0)被优化掉改用SwitchToThread()或WaitForSingleObject事件对象HTTPS 请求无法捕获HTTP 可捕获HTTPS 全无日志目标进程使用WinHttpSendRequest或InternetWriteFile而非send需额外 hookWinHttp或Crypt32.dll相关函数5.3 一个实用技巧用 OutputDebugString 实现零文件依赖的实时调试避免频繁读写磁盘影响目标进程性能可用OutputDebugString将数据发给调试器如 Visual Studio 或 DbgView// 替代 fprintf wchar_t debugBuf[4096]; swprintf_s(debugBuf, L[SEND %d] %.*S, len, min(len, 200), buf); // %S 表示宽字符字符串自动转换 OutputDebugStringW(debugBuf);提示OutputDebugString开销极低内核态写入环形缓冲区且DbgView可设置过滤器如SEND关键字高亮比文件日志更适合作为开发期验证手段。发布时再切换为文件写入。本文还有配套的精品资源点击获取
RELATED

相关推荐

OpenMetadata UI 代码质量门禁:从“新增代码必须干净”到 SonarCloud Clean-as-You-Code 的完整落地实践

OpenMetadata UI 代码质量门禁:从“新增代码必须干净”到 SonarCloud Clean-as-You-Code 的完整落地实践

OpenMetadata UI 代码质量门禁:从“新增代码必须干净”到 SonarCloud Clean-as-You-Code 的完整落地实践 【免费下载链接】OpenMetadata The Open Context Layer for Data and AI , OpenMetadata is the open platform for building trusted data context and busin…

📅 2026/9/14 5:20:40
Vector v0.24.0 版本深度解读:OpenTelemetry 初探、VRL 类型系统强化与可观测性数据管道演进

Vector v0.24.0 版本深度解读:OpenTelemetry 初探、VRL 类型系统强化与可观测性数据管道演进

Vector v0.24.0 版本深度解读:OpenTelemetry 初探、VRL 类型系统强化与可观测性数据管道演进 【免费下载链接】vector A high-performance observability data pipeline. 项目地址: https://gitcode.com/GitHub_Trending/vect/vector 导读 本文以 Vector 官…

📅 2026/9/14 5:20:40
oneAPI 图像识别教程,这次用 TaoToken 让 Codex 走通 resnet50 推理

oneAPI 图像识别教程,这次用 TaoToken 让 Codex 走通 resnet50 推理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/14 5:20:40
MORE NEWS

更多资讯

📰

MyEMS+CNN-LSTM:设备预测性维护从零到92%准确率的实战

接到值班同事电话的时候是早上六点半,三号空压机报警停机。我到现场打开柜门,轴承已经烧得发蓝,润滑油碳化成一圈黑渣。事后算账:非计划停机八小时,后段注塑产线全部等料,损失够买两台新电机。那会儿我就在…

📰

任务调度多数据库兼容实战:分布式锁从MySQL到PostgreSQL的迁移封装

这是我自己做的任务调度组件系列的第五篇。前几篇分别讲了基础实现、集群化改造、MySQL 锁方案和线程池参数调优,今天这篇收尾,重点说透一件事:当你的调度需要面对 MySQL 以外的数据库时,那套“数据库锁 线程池”的方案怎么封装才…

📰

同样用AI写论文:有人靠大模型包全流程AIGC率一路飘红,有人按分工表选工具顺利定稿,差距到底在哪

又到论文季,很多同学的操作是:用大模型列提纲、生成初稿,再让它“润色得学术一点”,最后提交检测时,AIGC率却一路飘红。 问题往往不是AI不够强,而是用同一类工具完成了所有环节。2026年的AI工具已经明显分…

📰

DMM5565同步采样原理与高精度电参数测量实战指南

1. DMM5565不是万用表,而是精密电参数测量系统的“指挥官”很多人第一次看到DMM5565这个型号,下意识就把它当成一台高级数字万用表——毕竟名字里带“DMM”(Digital Multimeter),面板上也有电压、电流、电阻档位标识。…

📰

基于元胞自动机的MATLAB疏散程序设计与实现

简介:基于元胞自动机的人员疏散Matlab仿真程序,以带有GUI界面的形式呈现,适合需要学习元胞自动机建模或开展应急疏散模拟的研究人员与学生,尤其降低了无编程基础用户的使用门槛。模型将空间离散为网格元胞,元胞状态涵盖…

📰

Windows用户态API Hook实战:拦截send/recv网络调用

简介:这是一份面向Windows逆向工程师、网络安全研究人员及系统级开发者的API Hook实战工具包,聚焦于拦截指定进程的网络收发行为,解决进程级网络通信监控与调试难题。资源共22个文件,包含8个头文件(h)与4个…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬