关于 SPI深入探讨
扫描二维码
随时随地手机看文章
这几天正在完善原来写的一个SPI防火墙(准备将原来的程序再削减一些,原来的太多太大了)。在写安装代码的时候发现SPI的理论虽然简单,但那些枚举,安装,排序函数(WSCEnumProtocols,WSCInstallProvider,WSCWriteProviderOrder,WSCDeinstallProvider)的使用和理解实在是太繁琐晦涩了,通过《Windows防火墙与网络封包截获技术》一书知道了SPI的核心其实就是注册表的操作,但书上只提供了一个基础服务替换的例子(将基础服务的WSAPROTOCOL_INFOW结构备份,DLL地址改为我们需要的DLL,然后在我们的DLL中再读取备份的WSAPROTOCOL_INFOW结构,加载基础服务,这个过程类似于陷阱API的过程),如果这个时候使用工具查看的话,可以看到完全不同于系统的SPI信息。对于协议链的操作,书上还是采用MSDN上面的标准方法。既然核心知道了,所以就试验了一下,发现很多地方其实没有必要象书上说的那么繁琐,对于分层服务和协议链都可以采用直接操作注册表的方法,简单明了。而且纠正了书上一个偏颇的论断:安装一个协议链必须同时安装两个WSAPROTOCOL_INFOW结构,一个是分层服务,以便获取WinSock目录ID,然后再安装协议链,使用获取的WinSock目录ID来填充ProtocolChain的ChainEntries数组!!我手工构造一个WSAPROTOCOL_INFOW结构并直接写入注册表中(只有协议链而没有分层协议),发现随便使用一个没有重复的入口ID,这个SPI
DLL一样可以有效。根据试验的结果反推过去,我们可以初步的得到SPI的工作方式。而根据得到猜想,我写出了一个自己的安装/卸载函数,发现可以很好的工作,抛弃了那些烦杂的函数和不停的遍历,简单明了。
下面简单介绍推导过程和原理,至于其中的代码,限于篇幅,需要者可以联系我。
先介绍一下这里使用的工具-Sundy网络监控工具(又一个半成品^_^):
图片:spi1.jpg
这个工具主要就是枚举注册表:HKEY_LOCAL_MACHINESystemCurrentControlSetServicesWinSock2ParametersProtocol_Catalog9Catalog_Entries下面的子键(名字为12位的字符串,由000000000001开始的,然后往后增加),然后将获取的WSAPROTOCOL_INFOW结构显示出来。我开始开发的一个个人防火墙采用的标准的安装卸载方法,获取的LSP状态如下:
========================================
序号:1
LSP名称:SUNDYFW [TCP/IP]
DLL路径:D:我需要开发的软件firewallSFWHOOK.dll
提供者GUID:{B49214F4-A941-45E6-84DD-5B4D40993551}
WinSock目录入口:1207
ChainLen:2
WinSock目录顺序0:1206
WinSock目录顺序1:1002
协议:IPPROTO_UDP
序号:2
LSP名称:SUNDYFW [UDP/IP]
DLL路径:D:我需要开发的软件firewallSFWHOOK.dll
提供者GUID:{B49214F4-A941-45E6-84DD-5B4D40993551}
WinSock目录入口:1208
ChainLen:2
WinSock目录顺序0:1206
WinSock目录顺序1:1001
协议:IPPROTO_TCP
序号:3
LSP名称:SUNDYFW [RAW/IP]
DLL路径:D:我需要开发的软件firewallSFWHOOK.dll
提供者GUID:{B49214F4-A941-45E6-84DD-5B4D40993551}
WinSock目录入口:1209
ChainLen:2
WinSock目录顺序0:1206
WinSock目录顺序1:1003
协议:IPPROTO_IP
序号:4
LSP名称:MSAFD Tcpip [TCP/IP]
DLL路径:%SystemRoot%system32msafd.dll
提供者GUID:{E70F1AA0-AB8B-11CF-8CA3-00805F48A192}
WinSock目录入口:1001
ChainLen:1
协议:IPPROTO_TCP
序号:5
LSP名称:MSAFD Tcpip [UDP/IP]
DLL路径:%SystemRoot%system32msafd.dll
提供者GUID:{E70F1AA0-AB8B-11CF-8CA3-00805F48A192}
WinSock目录入口:1002
ChainLen:1
协议:IPPROTO_UDP
序号:6
LSP名称:MSAFD Tcpip [RAW/IP]
DLL路径:%SystemRoot%system32msafd.dll
提供者GUID:{E70F1AA0-AB8B-11CF-8CA3-00805F48A192}
WinSock目录入口:1003
ChainLen:1
协议:IPPROTO_IP
......(略去序号7-34的内容)
序号:35
LSP名称:SUNDYFW
DLL路径:D:我需要开发的软件firewallSFWHOOK.dll
提供者GUID:{66426760-8DA1-11CF-8736-00AA00A485EA}
WinSock目录入口:1206
ChainLen:0
协议:IPPROTO_UDP
========================================
这个SPI可以非常好的工作,任何对于网络的访问都会出现类似下图的询问界面(很明显,图中例子勾住了WSPSend函数了):
图片:spi2.jpg
我们可以发现上面的信息,最后一个是一个ChainLen:0的WSAPROTOCOL_INFOW结构,说明它是一个分层服务,根据书上说的,这个服务是没有作用的(因为它位于注册表中最后面,WS2_32.dll是不会加载它的,除非其他中间分层服务调用。),在这里安装仅仅是为了取得它的WinSock目录入口ID-1206,而序号1、2、3是我们真正的入口(协议链),ChainLen=2 根据第1、2、3的ChainEntries的数组我们发现了第一项就是1206,然后是基础提供者的入口ID(1001至1003)。这个时候,我反而纳闷了,难道就为了一个1206就要在安装这个我们并不需要的WSAPROTOCOL_INFOW结构吗??疑问解答的最好的方式就是自己动手了,我自己构造了一个WSAPROTOCOL_INFOW结构,并填上一样的内容,只是在1206的地方,我随便写了一个1088(不是通过安装分层服务获得的,包括结构的CataLogEntryID的1188都是我自己硬编码进去的哟):
图片:spi3.jpg
然后使用注册表操作函数将这个结构直接写到第一项目,然后再次使用“Sundy网络监控”来枚举看看新的协议信息:
=====================================================
序号:1
LSP名称:SUNDQ Tcpip [TCP/IP]
DLL路径:D:我需要开发的软件firewallSFWHOOK.dll
提供者GUID:{B49214F4-A941-45E6-84DD-5B4D40993551}
WinSock目录入口:1188
ChainLen:2
WinSock目录顺序0:1088
WinSock目录顺序1:1001
协议:IPPROTO_TCP
序号:2
LSP名称:MSAFD Tcpip [TCP/IP]
DLL路径:%SystemRoot%system32msafd.dll
提供者GUID:{E70F1AA0-AB8B-11CF-8CA3-00805F48A192}
WinSock目录入口:1001
ChainLen:1
协议:IPPROTO_TCP
序号:3
LSP名称:MSAFD Tcpip [UDP/IP]
DLL路径:%SystemRoot%system32msafd.dll
提供者GUID:{E70F1AA0-AB8B-11CF-8CA3-00805F48A192}
WinSock目录入口:1002
ChainLen:1
协议:IPPROTO_UDP
序号:4
LSP名称:MSAFD Tcpip [RAW/IP]
DLL路径:%SystemRoot%system32msafd.dll
提供者GUID:{E70F1AA0-AB8B-11CF-8CA3-00805F48A192}
WinSock目录入口:1003
ChainLen:1
协议:IPPROTO_IP
序号:5
LSP名称:RSVP UDP Service Provider
DLL路径:%SystemRoot%system32rsvpsp.dll
提供者GUID:{9D60A9E0-337A-11D0-BD88-0000C082E69A}
WinSock目录入口:1004
ChainLen:1
协议:IPPROTO_UDP
序号:6
LSP名称:RSVP TCP Service Provider
DLL路径:%SystemRoot%system32rsvpsp.dll
提供者GUID:{9D60A9E0-337A-11D0-BD88-0000C082E69A}
WinSock目录入口:1005
ChainLen:1
协议:IPPROTO_TCP
......(略去序号7-31的内容)
序号:32
LSP名称:MSAFD NetBIOS [DeviceNetBT_Tcpip_{E0E321F4-BE2F-4BA2-995C-717E9C0B2285}] DATAGRAM 12
DLL路径:%SystemRoot%system32msafd.dll
提供者GUID:{8D5F1830-C273-11CF-95C8-00805F48A192}
WinSock目录入口:1153
ChainLen:1
协议:协议定义值:-12
=====================================================
通过现在的协议表格我们可以看到我们只是增加了一个WSAPROTOCOL_INFOW结构,测试后发现一样可以完成需要的工作。(保证ChainEntries[n]为所需要的服务的EntryID即可,当ChainLen为2时候n为2-1=1,这里n是1,TCP的EntryID为1001)。以上测试只是证明书上的一个论断偏颇,并可以就此写出自己的安装删除例程。下面根据自己的开发经验来讲述一下SPI的加载工作过程。
SPI的最上层是WS2_32.dll,它为应用程序提供一个统一的一致的开发界面(就是我们常用的那些套接字操作函数),而最下面的工作又是由TDI提供的,中间采用的分层服务提供方式,便于第三方插入自己的例程来处理一些东西。这种编程思想很有效,它就好比到邮局寄信一样,只要按照信封格式写好地址等信息且这些信息是正确的,那么邮局就可以保证信可以寄到目的地,而中间的过程可以采用空运,火车,汽车甚至是走路投递的方式。现在WS2_32.DLL接收到用户Application的套接字操作,它将根据套接字的类型枚举注册表键,获取最上面的(或者说第一个符合条件的)符合条件的子键,然后读取它的DLL地址和WSAPROTOCOL_INFOW的内容,根据DLL地址载入DLL,然后查找WSPStartup函数地址,找到后直接将查找到的WSAPROTOCOL_INFOW传入其中,由WSPStartup函数来处理这些,其实WSPStartup函数也将完成大致的过程,调用再下一层的DLL来完成这个链,之至最后进入基础服务层,这个时候基础服务将调用系统服务开始真正的数据处理了。下面是注册表中相应的项目图:
图片:spi5.jpg
上面的试验还证明了,WS2_32.dll也是通过注册表的顺序来循环遍历加载SPI DLL的,我们现在可以描述几个MSDN提供的API的工作内容了:
WSCEnumProtocols:遍历枚举注册表中指定的子键项目并返回子键的数量;
WSCInstallProvider:新建一个子键(位于最后面),并将指定的WSAPROTOCOL_INFOW写入到其中,安装完毕后将位于最后面(例如000000000031后面的就是000000000032)。
WSCWriteProviderOrder:根据提供的数组的顺序来重新排序并写入注册表中。
WSCDeinstallProvider:删除指定的注册表项目,并按顺序整理项目名称。
知道了这些函数的具体的操作,你也可以熟练的使用这些函数。如果你象我一样,什么都喜欢自己体验一遍的话,你也可以很容易的写出自己版本的枚举,安装,排序,删除函数了,这样就能从编程中得到那份少有的乐趣了^_^