2024年6月14日发(作者:)

Windows下基于SPI的网络数据包拦截的设计与实现

摘 要:基于TCP、UDP协议的网络应用程序如果在内部对代码段进

行检查或修复系统挂钩,通过HOOK API截包技术基本失效,在这种

情况下,使用WINSOCK2 SPI技术就完全胜任对特定网络应用程序数

据包的拦截任务,而且实现简单效率高,可移植性强。

关键词:SPI;服务提供者;数据包过滤;HOOK

0 引言

随着网络技术的飞速发展, 网络截包技术成为网络安全应用

中的一项重要技术.通过网络截包技术可以截获来自网络的数据包,根

据需要对网络数据包进行过滤,协议转换和截取报文分析,典型的应用

有网络防火墙和代理服务器等。

根据TCP/IP协议在Windows网络架构中实现的特点,在Windows

系统环境下,实现网络数据包截获的方式有多种.在用户模式下有

HOOK API截包技术, WINSOCK 2000包过滤技术和WINSOCK2 SPI

等技术.在内核模式下有NDIS中间驱动程序、NDIS HOOK Driver、

TDI(传输驱动接口)过滤驱动程序等技术。

我们对Windows系统中用户模式下使用HOOK API截包技术非

常熟悉,而且在软件编程中经常使用这一技术对基于TCP、UDP协议

的网络应用程序(如网络游戏,浏览器,FTP客户端等)截包分析,以实现

软件特定功能.但是基于TCP、UDP协议的网络应用程序如果在内部

对代码段进行检查或修复系统挂钩,使得我们不能挂钩特定的API函

数(如send()或recv()等),在这种情况下HOOK API截包技术基本失效。

为增强程序的可移植型,我们又不想涉及驱动。在这种情况下,使用

WINSOCK2 SPI技术就完全胜任对特定网络应用程序数据包的拦截

任务,而且实现简单效率高。文中将详细介绍Windows下基于SPI技

术的网络数据包拦截的设计与具体实现。

1 WINSOCK2 SPI 技术原理

1.1 SPI概述

Winsock2服务提供者接口(Service Provider Interface,简称SPI)

是Winsock API的补充。服务提供者接口(SPI)是应用程序使用的服

务,其本身不是应用程序,它的作用是向加载这个服务的应用程序导

出自己。

Winsock2符合Windows开放服务体系(Windows Open Service

Architecture, WOSA)模式,允许第三方服务提供者插入,而客户应

用程序和Winsock2 DLL(Ws2_)可以不用做任何改动。Winsock2

的体系结构和协议层次如图1所示。

图1 Winsock2体系结构和协议层次

SPI是一种标准接口,由传输服务提供者(Transport Service

Provider,简称TSP)和命名空间服务提供者(Name Space Providers,

简称NSP)两个部分组成。任何实现了这种标准接口的传输服务提供

者和命名空间服务者都可以被系统调用。用户如果提供自己的TSP

或者

NSP,就可以截获网络数据包,自己实现对数据包的处理。

1.2 传输服务提供者(Transport Service Provider)

传输服务提供者(通常是指堆栈协议)是提供建立连接,传输数据,

行使流控制,出错控制的服务.它有两种类型:分层服务提供者(Layered

Service Provider)和基础服务提供者(Base Service Provider).基础服务

提供者负责实现传输协议的真正细节,它导出Winsock接口,直接实

现协议(如TCP/IP提供者)。分层提供者本身是DLL,它向上导出所

有的SPI函数供WS2_调用,在内部通过调用基础服务提供者

实现这些SPI.分层服务提供者将自己安装到Winsock目录中基础服务

提供者上面,截取来自应用程序中的Winsock API调用。用户创建套

接字时,套接字创建函数(如socket)就会在Winsock目录中寻找一个

合适的协议,然后调用此协议的提供者到处的函数完成各种功能。上

面提供的协议层次图显示了应用程序,分层服务提供者和基础服务提

供者之间的联系。

2 用SPI截包的设计与实现

2.1 安装分层服务提供者(LSP)

在实现LSP之前,首先要将LSP安装到Winsock目录,必须在

Winsock目录中安装两种协议--分层协议和协议链。安装分层协议是

为了获取一个Winsock库分配的目录ID号,以便在协议链中标识自

己的位置。协议链是Winsock目录中LSP的真正入口,链中包含了

自己分层协议的目录ID号和下层提供者的目录ID号,可以构建一个

WSAPROTOCOL_INFOW结构。安装LSP的函数是

WSCInstallProvider,只要为它提供LSP的GUID,DLL位置,描述它支

持的协议的一个或多个WSAPROTOCOL_INFOW结构即可。新的

LSP安装到Winsock目录之后,在枚举时,它默认出现在Winsock目

录的结尾,可能不会被默认调用,需要调用WSCWriteProviderOrder

函数重新排序目录。为了演示,下面将提供把LSP安装到UDP提供

者之上程序代码:

int InstallProvider(WCHAR *wszDllPath) {

int nError = NO_ERROR;

LPWSAPROTOCOL_INFOW pProtoInfo;

WSAPROTOCOL_INFOW UdpLayInfo, UdpChainInfo;//要安装的

分层协议,协议链

DWORD dwUdpOrigId, dwLayId;

int nProtocol;

pProtoinfo = GeProvider(&nProtocol);//枚举所有服务提供者

for(int i = 0; i < nProtocol; i++) {

if(pProtoInfo[i].iAddressFamily==AF_INET&&pProtoInfo

[i].iProtocol==IPPROTO_UDP){

memcpy(&UdpChainInfo, &pProtoInfo[i], sizeof(UdpLayInfo));

iceFlags1

=

iceFlags1&~XP1_IFS_HANDLES;

dwUdpOrigId = pProtoInfo[i].dwCatalogEntryId;//保存原来入口

ID

break;

}

}

memcpy(&UdpLayInfo, &UdpChainInfo, sizeof(UdpLayInfo));

wcscpy(ocol, L"TestLSP");//"TestLSP"为自己的

LSP名称

en = LAYERED_PROTOCOL;

//0,暗示是一个分层协议

iderFlags |= PFL_HIDDEN;

//安装

if(::WSCInstallProvider(&ProviderGid,wszDllPath,&UdpLayInfo,

1, &nError)==SOCKET_ERROR) return nError;

FreeProvider(pProtoInfo);

pProtoInfo =GetProvider(&nProtocol);//重新枚举协议,获取分层

协议的目录Id号

for(int i = 0; i < nProtocol; i++){

if(memcmp(&pProtoInfo

[i].ProviderId,&ProviderGid,sizeof(ProviderGid))==0){

dwLayId = pProtoInfo[i].dwCatalogEntryId;

break;

}

}

//安装协议链

WCHAR wszChainName[WSAPROTOCOL_LEN+1];

Swprintf(wszChainName, L"%ws over %ws", L"TestLSP",

tocol);

wcscpy(ocol, wszChainName);

if(en == 1){//是基础协议

ntries[1] = dwUdpOrigId;

} else {

for(int i = en; i>0; i--)

ntries[i] =

ntries[i-1] ;

}

en++;

//将分层协议置于协议链的顶层

ntries[0] = dwLayId ;

GUID ProviderChainGid; //获取一个Guid并安装

if(::UuidCreate(&ProviderChainGid)==RPC_S_OK)

if(::WSCInstallProvider(&ProviderChainGid,wszDllPath,&UdpChainInf

o, 1,&nError)==SOCKET_ERROR) return nError;

FreeProvider(pProtoInfo);

pProtoInfo = GetProvider(&nProtool);//重新枚举协议

DWORD dwIds[20];

int nIndex = 0;

for(int i=0; i

if(pProtoInfo[i].en>1)&&(pProtoInfo

[i].ntries[0]==dwLayId)) dwIds[nIndex++]

=pProtoInfo[i].dwCatalogEntryId;

for(int i=0; i

if(pProtoInfo[i].en<=1)&&(pProtoInfo

[i].ntries[0]==dwLayId)) dwIds[nIndex++]

=pProtoInfo[i].dwCatalogEntryId;

//重新排序Winsock目录

nError = ::WSCWriteProviderOrder(dwIds, nIndex);

FreeProvider(pProtoInfo);

Return nError;

}

2.2 实现LSP

Winsock2 LSP实现在标准的Windows DLL中,当应用程

序调用Winsock2 API时,WS2_最终会调用特定服务提供者中

对应的Winsock2 SPI函数来完成制定功能,每个LSP必须实现和导出

WSPStartup函数,所有其它的SPI 函数都由LSP的分派表导出。

WSPStartup函数主要是根据协议链找到下层提供者,初始化下层提

供者,取得SPI服务函数的指针,在向上返回指针之前,可以用自己

定义的函数指针覆盖它,实现截获Winsock调用。WSPStartup实现

代码如下:

WSPPROC_TABLE g_NextProcTable;

int WSPAPI WSPStartup

(WORD wVersionRe2

quested,LPWSPDATA lpWSPData,

LPWSAPROTOCOL-INFOW

lpProtocolInfo,WSPUPCALLTABLE UpcallTable,

LPWSPPROCTABLE lpProcTable){

//szDllPath为我们提供的服务者路径

HINSTANCE hInst = ::LoadLibrary(szDllPath);

if(!hInst)

return 0;

//导入下层提供者的WSPStartup函数

LPWSPSTARTUP Func = (LPWSPSTARTUP)∷Get

ProcAddress(hInst", WSPStartup") ;

int nret = WSPStartup Func (wVersionRequested , lpWSPData ,

lpProtocolInfo ,UpcallTable , lpProcTable) ;

//保存下层提供者函数表

g_NextProcTable = *lpProcTable ;

//HOOK感兴趣的函数,这里仅HOOK了WSPSendTo函数

lpProcTable->lpWSPSend = WSPSendTo ;

return nret ;

}

WSPSendTo函数的实现代码如下:

int WSPAPI WSPSendTo(SOCKET S, LPWSABUF lpBuffers,

DWORD dwBufferCount,

LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const

struct sockaddr FAR* lpTo, int iTolen, LPWSAOVERLAPPED

lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE

lpCompletionRoutine, LPWSATHREADID lpThreadId, LPINT lpErmo)

参考文献:

[1] 张越.网络程序设计[M].北京:人民邮电出版社,2006.

[2] 汪国洋,王景中.基于SPI的访问控制技术[J].计算机

应用,2003.

[3] 葛子昂,周靖,廖敏.Windows核心编程[M].北京:清

华大学出版社,2008.