尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

LSP注入实战:用Winsock SPI拦截网络请求的DLL接口

发布时间:2026/9/26 4:34:19

资讯中心
01
ARTICLE

LSP注入实战:用Winsock SPI拦截网络请求的DLL接口

LSP注入实战:用Winsock SPI拦截网络请求的DLL接口
简介在Windows网络编程中拦截应用层网络请求常需面对闭源程序、加密协议等难题。LSP注入Layered Service Provider分层服务提供程序作为一种基于Winsock2 SPI机制的用户态DLL注入技术能够在不改动目标程序的前提下通过协议链自动加载到进程空间从而获取所有Winsock调用的上下文。它本质上是在传输提供程序与基础协议栈之间插入中转层既能用于流量审计、内容过滤、协议分析也可作为理解Windows网络栈分层设计的入口。与API Hook和WFP驱动相比LSP无需内核权限开发门槛低但需注意64/32位Catalog隔离及卸载顺序等陷阱。本文围绕LSP注入的原理、最小工程实现、数据记录与避坑验证展开为流量监控与协议分析实践提供参考。1. LSP注入是什么一个能“偷看”所有网络请求的DLL接口做网络流量监控或协议分析的人多半遇到过这种尴尬目标程序是闭源的没法改它的代码可又想知道它跟服务器之间到底发了什么、收了什么。抓包工具能看到包但包是加密的或是二进制协议还原不出应用层语义。LSP注入就是一条老派但依旧有效的路——LSPLayered Service Provider分层服务提供程序是Winsock2 SPI体系里的一个可注入DLL进程只要调用了socket相关的API系统就会按协议链自动把这个DLL加载进去之后你就能在收发路径上插入自己的处理逻辑。它解决的核心问题是“不改程序、不碰驱动、在用户态拿到所有Winsock调用的上下文”。适合做流量审计、内容过滤、协议学习、内部系统监控这类场景。新手能从这里理解Windows网络栈的分层思路熟手则能用它补上对SPI机制和协议链的边界认知。本文会从原理、工程实现、安装部署一路讲到四五个经典的坑最后给出验证和进程级过滤的技巧供直接照抄。2. LSP注入的底层原理Winsock2 SPI与分层服务提供程序2.1 从API到SPIws2_32.dll只是转发层Windows的Winsock2设计里程序员平时调用的socket、send、recv这些函数并不直接访问协议驱动。它们都在ws2_32.dll里真正的协议实现被抽象成一组SPIService Provider Interface服务提供程序接口。ws2_32.dll的角色更像一个路由壳它把一次socket调用转交给配置好的“传输提供程序”。这个提供程序链在系统中注册在一个叫Winsock Catalog的目录里目录项就是一个个服务提供程序的描述。LSP是插在这一串provider之间的那一环。它本身也是一个DLL系统通过一个约定好的入口函数WSPStartup把它初始化。只要协议链上挂载了这个LSP进程里任何一个socket操作创建、连接、收发、关闭都会先经过它。严格说LSP不是传统意义的“注入”它不需要往进程空间里写代码而是靠Winsock自身的扩展机制被自动加载所以稳定性和兼容性比一般的远程线程注入高出不少。在讲实现之前建议先理解几个关键术语否则后续代码会看得一头雾水。每个传输提供程序在Catalog里对应一个WSAPROTOCOL_INFO结构里面记录服务类型、协议ID、链的层数。如果有多个LSP串起来WSAPROTOCOL_INFO里的ProtocolChain字段会记录整条链的组成顺序。系统通过这个链把一个socket的收发请求从上层一路传到最下面的基础传输提供程序比如TCP/IP的mswsock.dllLSP在其中扮演拦截中转的角色。2.2 协议链与Catalog条目LSP挂在哪一层LSP并不自己实现TCP/IP协议栈它必须依托一个基础传输提供程序才能工作。安装LSP时要做两件事一是在Catalog里注册一个新的分层providerLayered Provider二是把这个provider“夹”进一或多个基础传输提供程序的链里。这条链在Winsock2的术语里叫ProtocolChain链由若干分层provider和一个基础provider组成。用Winsock Catalog查看工具可以看到形如“分层服务提供程序 → TCP/IP”这样的条目。整个Catalog本身存在注册表里系统启动和进程初始化Winsock时会读取它。因此LSP的安装本质上就是把DLL路径和一个WSAPROTOCOL_INFO写进注册表对应的Catalog键值。微软提供的API是WSCInstallProvider和WSCWriteProviderOrder前者负责注册provider后者负责调整链的顺序。顺序很重要如果自己的LSP排在别的LSP后面先被调用的就是别人你的LSP可能在它的转发链里被包了一层。目录结构上还有一个易踩的坑64位系统和32位程序的Winsock Catalog并不互通注册表里分成了两个位置。这也是后面避坑章节要重点展开的问题。2.3 和API Hook、WFP驱动的边界对比很多人在做流量监控时会纠结技术选型最容易混淆的三条路线是LSP、API Hook/Inline Hook、WFP驱动。LSP只在Winsock协议的范畴内生效如果目标程序不走Winsock而是直接和驱动通信LSP是看不到的。API Hook可以覆盖更底层但要改写目标进程的指令容易被游戏保护或安全软件拦截也容易把自己的DLL搞出递归。WFPWindows Filtering Platform则是在内核态的过滤框架功能最强、性能最好但需要驱动签名、需要跨版本维护开发门槛高出一大截。对比下来LSP的定位是“用户态、全局生效、不需要提权驱动、能拿到应用层数据”的折中方案。适合在企业内网审计、自研协议栈分析、教学实验这些场景中做透明中间层。注意现在常说的LSP有时也指Language Server Protocol语言服务器协议那是编辑器里给代码补全用的东西比如Claude Code这类AI编程工具里就有它的身影。本文的LSP是Winsock分层服务提供程序两者只是首字母缩写碰巧一样千万别混。3. 搭一个最小LSP注入工程工程结构、导出函数与初始化顺序3.1 环境准备与DLL工程的最小结构开发LSP不需要特殊的SDKVisual Studio中的Windows桌面应用程序开发组件就够。工程类型选“动态链接库(DLL)”项目名称可以叫LspSandbox然后关掉预编译头语言标准选C14以上。核心产物只有一个DLL但建议把安装器单独做成一个控制台工程避免调试时反复改代码导致系统Catalog不稳定。一个LSP DLL至少需要三样东西DllMain入口、WSPStartup导出函数、以及一组实现了SPI接口的转发函数。WSPStartup是系统调用LSP的起点相当于LSP的黑匣子入口所有初始化都在这里完成。此外还需要一个.def文件或导出声明把WSPStartup按序号导出。很多初学朋友在这里犯迷糊以为DLL只要导出了WSPStartup就行其实还需要保证ws2_32.dll能找得到它。最简单的导出方式是在.def文件中写明导出符号如下所示LIBRARY LspSandbox EXPORTS WSPStartup如果不想维护.def文件也可以在代码里用#pragma comment(linker, /EXPORT:WSPStartupWSPStartup)声明导出。两种都行但.def文件更清晰我一般倾向于用.def。这个文件编译后不会出现在产物目录里它是一个编译指令让链接器把WSPStartup这个符号暴露出去供系统加载时绑定。3.2 WSPStartup入口接收调用表与协议信息接下来是最核心的入口函数。Winsock2 SPI规定系统的目录管理器会调用WSPStartup传入版本号、上层调用函数表UpCallTable、协议信息以及一个用于返回下层调用表的指针。LSP要做的事是“截胡”这个返回过程把下层表替换成自己的函数表但保留真正的下层函数指针之后自己的转发函数再调用真实下层完成最终收发。#include winsock2.h #include ws2spi.h #include windows.h #pragma comment(lib, ws2_32.lib) static WSPUPCALLTABLE g_UpCallTable; static LPWSAPROTOCOL_INFO g_ProviderInfo; static WSPPROC_TABLE g_TrueProcTable; static WSPPROC_TABLE g_NextProcTable; static int WINAPI MyWSPRecv( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, LPWSATHREADID lpThreadId, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCbKey) { int ret g_NextProcTable.lpWSPRecv( s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, lpCompletionRoutine, lpThreadId, lpCbKey); return ret; } int WSPAPI WSPStartup( WORD wVersion, WORD wHighVersion, LPWSPUPCALLTABLE lpUpCallTable, LPWSPPROTOCOL_INFO lpProtocolInfo, WSPPROC_TABLE lpProcTable, LPWSPPROC_TABLE lpNextProcTable) { // 保存上层回调表与协议信息 g_UpCallTable *lpUpCallTable; g_ProviderInfo lpProtocolInfo; // 保存系统给的真实下层函数表 g_TrueProcTable *lpNextProcTable; g_NextProcTable *lpNextProcTable; // 替换自己需要的几个函数 g_NextProcTable.lpWSPRecv MyWSPRecv; // 把替换后的表写回让上层调用者走我们的逻辑 *lpProcTable g_NextProcTable; return 0; } BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { if (ul_reason_for_call DLL_PROCESS_ATTACH) { // LSP会被系统加载到每个使用Winsock的进程这里不做重型初始化 DisableThreadLibraryCalls(hModule); } return TRUE; }这段代码的逻辑很直白先把系统传进来的真实下层表保存到g_NextProcTable然后把其中的lpWSPRecv换成自己的MyWSPRecv最后把替换后的整张表返回给系统。这样进程里后续的recv操作会先进入MyWSPRecv再在函数体内调用原始的下层实现。这里有几个参数值得单独说。wVersion和wHighVersion是Winsock的版本协商LSP一般直接接受不主动拒绝。lpUpCallTable是上层应用回调表用于异步完成通知大多数情况只保存不修改。lpProtocolInfo则是当前这条链对应的WSAPROTOCOL_INFO如果LSP安装在多条协议链上不同socket可能走不同条目建议把感兴趣的协议ID打印出来。最后lpNextProcTable才是真正要保留的“真实下层函数表”。注意结构体内部成员很多不要整表拷贝后再去修改个别成员修改完务必保证其他成员的原值不被破坏。3.3 需要用到的关键数据结构WSPPROC_TABLE是一张函数指针表定义了LSP能替换的全部SPI函数。现实中不需要全部替换只要替换和业务相关的几个比如lpWSPRecv、lpWSPSend、lpWSPCloseSocket。其余的保持原值即可。以下表格列出最常用的几个成员方便对照成员名对应SPI函数说明lpWSPRecvWSPRecv接收数据主要劫持点lpWSPSendWSPSend发送数据主要劫持点lpWSPConnectWSPConnect拦截连接动作可做目标地址过滤lpWSPCloseSocketWSPCloseSocket拦截关闭动作可清理上下文lpWSPEventSelectWSPEventSelect事件选择必要时可跳过这段代码可以编译成一个最小DLL完成“加载但不干坏事”的验证。真正的数据拦截逻辑还需要在MyWSPRecv里补充这就是下一章的内容。4. 实现一个能记录通信数据的LSPrecv/send转发与日志落地4.1 在WSPRecv里拿到数据并写出日志当我们把g_NextProcTable.lpWSPRecv替换成自己的函数之后进程里所有socket的接收数据都会经过MyWSPRecv。此时我们要做的第一件事是先调用真实下层函数让系统真正去收数据然后对收到的缓冲区进行检查和记录。注意WSPRecv的缓冲区管理机制lpBuffers可能一次对应多个WSABUF每个WSABUF有自己的长度和指针必须全部遍历。static void WriteLog(const char* tag, SOCKET s, WSABUF* buffers, DWORD count, DWORD totalLen) { FILE* fp fopen(C:\\Temp\\LspLog.txt, a); if (!fp) return; SYSTEMTIME st; GetLocalTime(st); fprintf(fp, [%02d:%02d:%02d.%03d] %s socket%llu len%lu\n, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, tag, (unsigned long long)s, (unsigned long)totalLen); for (DWORD i 0; i count; i) { if (buffers[i].buf buffers[i].len 0) { // 只记录前256字节避免日志膨胀 DWORD n buffers[i].len 256 ? 256 : buffers[i].len; for (DWORD j 0; j n; j) { fprintf(fp, %02X , (unsigned char)buffers[i].buf[j]); if (j % 16 15) fprintf(fp, \n); } fprintf(fp, \n); } } fclose(fp); } static int WINAPI MyWSPRecv( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, LPWSATHREADID lpThreadId, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCbKey) { // 先调用真实下层函数完成数据接收 int ret g_NextProcTable.lpWSPRecv( s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, lpCompletionRoutine, lpThreadId, lpCbKey); // 接收成功且有数据时记录日志 if (ret 0 lpNumberOfBytesRecvd *lpNumberOfBytesRecvd 0) WriteLog(RECV, s, lpBuffers, dwBufferCount, *lpNumberOfBytesRecvd); return ret; }关于这段代码的调用时机需要特别注意必须先把参数完整转交给真下层函数再读取缓冲区内容。因为有些协议栈实现会在调用后填充缓冲区我们的日志代码读到的才是实际收下来的数据。如果先读缓冲区再调下层可能拿到的是上一次的数据或未初始化的内存。对于重叠IOOverlapped IO的情况WSPRecv可能返回WSA_IO_PENDING数据要等完成例程或事件通知后才有效此时lpNumberOfBytesRecvd指向的值没有实际意义直接记录会导致日志里出现大量长度为0的噪声。处理办法是遇到WSA_IO_PENDING就不记录或者在完成回调里补一条记录。4.2 实现对发送数据的拦截WSPSend接收是下半场发送是上半场。如果要分析客户端上传了什么、或者做内容过滤必须在WSPSend里动手。逻辑和接收一样先调真实下层发送函数再把要发的数据写进日志。发送日志放在调用之后有一个好处就是只要下层函数成功返回说明数据已经交给协议栈记录下来的内容是真实发出的数据。static int WINAPI MyWSPSend( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, LPWSATHREADID lpThreadId, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCbKey) { int ret g_NextProcTable.lpWSPSend( s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, lpFlags, lpOverlapped, lpCompletionRoutine, lpThreadId, lpCbKey); if (ret 0 lpNumberOfBytesSent *lpNumberOfBytesSent 0) WriteLog(SEND, s, lpBuffers, dwBufferCount, *lpNumberOfBytesSent); return ret; }和WSPRecv唯一的差别是文件标签从“RECV”换成了“SEND”函数签名里的完成通知成员保持一致。调完真实下层后如果返回值是SOCKET_ERROR且错误码是WSA_IO_PENDING同样不能记录因为数据还没真正发出。这种做法在非阻塞模式下比较常见也是数据记录容易漏掉的一环。想要处理阻塞和非阻塞两种模式可以把记录动作移到WSPEventSelect的FD_WRITE/FD_READ通知里但工程复杂度会上升不少实验阶段用同步判断就够了。4.3 编译完成后如何把LSP装进系统目录写完了DLL只是第一步得把这个LSP挂载到Winsock Catalog里才能真正生效。挂载动作可以用一个独立的安装器完成核心是调用WSCInstallProvider和WSCWriteProviderOrder。这两个API在ws2spi.h里声明需要链接ws2_32.lib。安装过程本质上是在注册表里创建一个新的分层provider条目然后把它插到TCP/IP等基础provider的前面。#include winsock2.h #include ws2spi.h #include stdio.h #pragma comment(lib, ws2_32.lib) int main() { // 先找到TCP/IP基础provider的目录ID DWORD dwSize 0; WSAPROTOCOL_INFO protocols[32]; int count WSCEnumProtocols(NULL, protocols, dwSize, error); if (count SOCKET_ERROR) { printf(enum failed\n); return -1; } // 挑一个AF_INET流式套接字的基础provider比如TCP/IP int baseCatalog -1; for (int i 0; i count; i) { if (protocols[i].iAddressFamily AF_INET protocols[i].iSocketType SOCK_STREAM protocols[i].iProtocol IPPROTO_TCP) { baseCatalog protocols[i].dwCatalogEntryId; break; } } if (baseCatalog -1) { printf(no tcp provider\n); return -1; } // 构造分层provider的描述链结构指向基础提供程序 WSAPROTOCOL_INFO info { 0 }; info.dwServiceFlags1 XP1_IFS_HANDLES | XP1_MESSAGE_ORIENTED | XP1_SEQUENCED_PACKET; info.iAddressFamily AF_INET; info.iSocketType SOCK_STREAM; info.iProtocol IPPROTO_TCP; info.dwCatalogEntryId 0; // 安装时由系统分配 info.ProtocolChain.ChainLen 2; info.ProtocolChain.ChainEntries[0] 0; // 系统安装后回填本provider的id info.ProtocolChain.ChainEntries[1] baseCatalog; // 下一层是基础TCP/IP // 安装DLL提供的LSP int error 0; if (WSCInstallProvider(info, LC:\\Path\\LspSandbox.dll, NULL, error) SOCKET_ERROR) { printf(install failed: %d\n, error); return -1; } printf(install ok\n); return 0; }这段代码的逻辑是把LSP挂到TCP流式协议上链长是2表示一层LSP加一层基础provider。WSCInstallProvider的第一个参数需要传入一个WSAPROTOCOL_INFO其中ProtocolChain描述了我们这条链的组成。安装成功后系统会分配新的目录项ID并把这个LSP条目注册进Catalog但不会自动把它放到所有用TCP的应用前面。要让LSP真正排在前面还需要调用WSCWriteProviderOrder调整顺序把刚安装的provider的CatalogEntryId放到TCP/IP条目前面。这一步常常被初学者忽略导致DLL装好了却不生效——系统里存在这条链但应用默认选用的还是最原始的TCP/IP provider。顺序调整的代码不复杂就是枚举所有provider排序后传入一个目录ID数组。调整完需要重启使用Winsock的进程才生效。另外注意写安装器时要在64位和32位两种环境下分别做否则会出现“能装上但有些程序不走链”的诡异现象。5. LSP注入的4个经典坑现象、原因与解决5.1 装上了但进程完全不加载DLL这是最常见的翻车现场。现象是安装器执行成功日志文件也没有任何报错但目标进程里就是看不到DLL被加载。用进程查看器核对加载模块列表里根本没有你的LspSandbox.dll。原因往往是安装器里只调用了WSCInstallProvider没有调用WSCWriteProviderOrder。Catalog里确实出现了新条目但它的位置在所有基础provider后面进程初始化Winsock时选择了最常用的基础provider根本不会走到LSP。解决方法是补上顺序调整逻辑把LSP的目录ID通过WSCWriteProviderOrder排到最前面。然后在命令行执行netsh winsock show catalog确认分层provider条目下面确实链接了TCP/IP基础条目。这个命令输出里可以看到Catalog的完整排列非常直观。调整完记得重启浏览器或要监控的目标程序。5.2 卸载LSP后机器网络异常这是最让人心悸的坑。现象是卸载DLL或删除注册表条目后系统出现浏览器打不开网页、网络连接显示正常但数据传输停滞等大面积异常。原因是Winsock Catalog被写坏了系统里所有依赖Winsock的应用都在初始化时失败而Windows自身很多服务也依赖Winsock。LSP的卸载不是简单地删除DLL文件必须用WSCDeinstallProvider按目录ID移除条目否则链的指向会悬空。如果已经发生网络异常最可靠的处理是运行命令netsh winsock reset这个命令会把Winsock Catalog重置为系统初始状态清掉所有第三方协议提供程序。注意它会同时重置所有网络相关组件执行后需要重启系统。这个命令本身是Windows自带的安全恢复手段不是本文方案的扩展用来给LSP开发兜底很合适。经验是改Catalog前先做一个注册表导出备份出问题时导入回去能省很多事。5.3 64位系统下32位程序不走LSP现象比较隐蔽64位进程能正常记录到日志但同一个系统里运行一个32位的老程序它的流量完全看不到。这不能在代码里修而是Windows的Winsock Catalog本身就有两套。64位进程读的是原生注册表项32位进程读的是Wow6432Node下的对应项。如果安装器是在64位模式下编译并运行的那么它只会往64位Catalog里写32位进程自然不受影响。解决方法是把安装器做成两套或者用WOW64重定向机制在安装时分别写入两个Catalog。最简单可靠的做法是编译一版x86的安装器一版x64的安装器分别运行一次。DLL文件也分别输出到两个路径因为32位进程无法加载64位DLL反过来也是一样。至于做LSP的DLL本身必须编译两套x86版给32位进程用x64版给64位进程用安装时对应写入。这个坑如果不提前防往往要排查几个小时才能定位到架构差异上。5.4 日志里同一进程数据重复记录现象是往浏览器里访问一次页面日志文件却出现了两条相同的“SEND”记录。初看以为是自己的函数被递归调用了仔细排查才发现不是代码的问题而是Winsock Catalog里同一个LSP被挂载到了多条链上。比如你的LSP同时挂进了TCP/IP的IPv4链和IPv6链而应用创建了一个双栈socket底层实际走了两条链接收时两条LSP条目都执行了一次。另一种可能是安装时重复执行了多次安装器Catalog里留下了多个相同DLL的条目。解决方法是先枚举当前Catalog里所有同名产品的provider条目把重复项卸载掉再检查应用创建的socket类型针对双栈情况设置IPV6_V6ONLY。日志里加一行socket句柄和进程ID的输出会大幅提升排查效率。以前我还见过一次“每个包记录两次”的翻车最后定位到是系统里残留了另一个团队安装的同名LSP两个LSP串在一起同一个包被两层先后记录所以写日志时一定要带上provider的目录ID或进程ID方便日后续排查。提示LSP开发中遇到任何“看似正常但行为不对”的情况先查Catalog先看日志先确认架构再怀疑自己的转发逻辑。这条排查顺序能省掉大半晚上的时间。6. 进阶验证与进程级定向监控技巧先给一套能快速验证LSP是否生效的自检方法。在DLL里写一个独立的初始化标志文件比如WSPStartup被调用时在C:\Temp下创建LspLoaded_进程ID.txt然后启动记事本、浏览器这类一定会用到socket的进程观察目录里是否出现对应文件。加上时间戳后还能看出进程启动后多久被注入。如果目标程序迟迟没有创建文件先用netsh winsock show catalog确认排列顺序再用Process Explorer或任务管理器查看目标进程的已加载模块列表确认DLL是否在列表中。LSP装好后是全进程生效的但很多场景下我们只关心某个特定程序的行为其余进程的记录会撑爆日志文件。优化办法是在WSPStartup入口处判断调用者进程名不是目标进程就直接把原始调用表原样返回不做替换。Windows下获取当前进程名有多种方式推荐用GetModuleFileNameEx查询进程句柄或者用更底层的NtQueryInformationProcess取PEB里的ImagePath前者写法更标准后者更快。#include psapi.h #pragma comment(lib, psapi.lib) static bool IsTargetProcess() { // 当前进程的模块文件名仅提取名称部分 wchar_t path[MAX_PATH] { 0 }; DWORD pid GetCurrentProcessId(); HANDLE hProc OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); if (!hProc) return false; DWORD size MAX_PATH; if (QueryFullProcessImageNameW(hProc, 0, path, size) 0) { CloseHandle(hProc); return false; } CloseHandle(hProc); // 只要目标程序的exe名这里以trafficTool.exe为例 wchar_t* baseName wcsrchr(path, L\\); if (!baseName) return false; return _wcsicmp(baseName 1, LtrafficTool.exe) 0; } int WSPAPI WSPStartup(...) { // 非目标进程直接返回原表不劫持 if (!IsTargetProcess()) { *lpProcTable *lpNextProcTable; return 0; } // 目标进程才做替换逻辑 g_NextProcTable *lpNextProcTable; g_NextProcTable.lpWSPRecv MyWSPRecv; *lpProcTable g_NextProcTable; return 0; }这段代码的思路很清晰先确认当前进程是否为目标exe不是就直接把系统给的原始表返回劫持逻辑完全不加载。这样日志文件只会有目标进程的记录系统其它进程的属性、崩溃率、性能开销全部归零。wcsrchr用于截取最后一个反斜杠后的文件名注意如果路径中带有反斜杠序列C字符串写法需要写成双反斜杠。最后说一条实战教训LSP不是一个可以随意在开发机上反复测试的东西它活在系统全局网络栈里一次错误的Catalog写入就可能让整机“断网”。我给自己定下的规矩是所有的安装测试都在虚拟机里做测试前打一个系统还原点当作后悔药出问题可以秒回滚。真机上从未直接装过第一版DLL这习惯帮我躲过至少三次差点搞坏宿主机的险情。希望这篇文章的代码和排查路径能帮你避开这些坑顺利把第一个能记录数据的LSP跑起来。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。