简单分析用SPI实现防火墙

类别:VC语言 点击:0 评论:0 推荐:

                                       简单分析用SPI实现防火墙

  作者:snsins

如有转载,请注明并保持文章的完整

2002.12.5

--------------------------------------------------------------------------------------------------------

 

本来上次写了一个简单的SPI防火墙程序(只做了IP过滤功能,包过滤功能没有去做)后,已经放弃了对SPI的研究,但是最近有朋友问到SPI防火墙的实现,现在就再对SPI做一次详细点的分析和总结。

先看看防火墙的一般实现方式

首先是TCP/IP的结构(OSI7层模型)
--------
|应用层|------exe程序,比如ie
--------
|表示层|-------ws2_32.dll
--------
|会话层|-------SPI
--------        
|传输层|-------TDI(不能截获ICMP等协议的数据)
-------- 
|网络层|-------NDIS(可以截获所有的网络数据)
--------
|链路层|-------设备驱动
--------      
|物理层|-------网卡
--------
从上面我们可以很清楚地看到我们有多种方式实现防火墙,比如用HOOK API在表示层HOOK WINSOCKET的API函数,在会话层用WINDOWS提供的标准的SPI方式实现,SPI相对HOOK API方式要规范多了,而且功能也更强大,但是还是在user model里,。在传输层可以写驱动实现防火墙,虽然已经到了kernel model,但是和上面两种方式一样,过滤不了比如ICMP等协议的数据,因为ICMP等协议的数据并不经过传输层。这样我们可以看到,在WINDOWS里实现防火墙最标准最规范最强大的方式应该是在NDIS(也就是网络层实现),因为NDIS提供了一些规则,只要让我们来调用一些写好的函数来组织数据就可以了,功能不仅强大,而且相对下面链路层的设备驱动要简单,应该说在链路层也可以实现防火墙,但是觉得没有必要,太复杂了。

现在回过头来看看SPI的结构方式
--------------------
|    ws2_32.dll    |
--------------------
|        SPI       |
--------------------
|        SPI       |--------可以有很多层,就是所谓的分层服务提供者
--------------------
|   基础服务提供者 |
--------------------

服务提供者有两种,一种是分层服务提供者,一种是基础服务提供者,上面这个图不是很准确,我们这样来理解,我们写的分层服务提供者必须调用基础服务提供者或者下面一层的分层服务器提供者,然后把请求提交到他上面的一层(上面的一层可能是另外一个分层服务提供者,也可能是ws2_32.dll)。而我们写的基础服务提供者必须调用系统基础服务提供者,然后把请求提交到ws2_32.dll。请注意,系统里可能不仅仅安装了我们的基础服务提供者,也安装了别人写的基础服务提供者。在安装多个分层服务提供者和多个基础服务提供者的情况下,这两者的组织方式是不同的
如下图

-------------------------------------------------------------
|                        ws2_32.dll                        |
-------------------------------------------------------------
          |                         |                      |
---------------------               |                      |
|别人的分层服务提供者|              |                      |
---------------------               |                      |
          |                         |                      |
---------------------               |                      |
|别人的分层服务提供者|              |                      |
----------------------              |                      |
          |                         |                      |
---------------------               |                      |
|我们的分层服务提供者|              |                      |
----------------------              |                      |
          |                         |                      |
---------------------     ----------------------  ---------------
|别人的分层服务提供者|    |别人的基础服务提供者| |我们的基础服务提供者|
----------------------    ----------------------  ---------------------
          |                         |                      |
----------------------------------------------------------------
|                 系统基础服务提供者                            |
————————————————————————————————

如果你连上面这个图看了还是不懂我就没办法了。

从上面可以看出,这个SPI的组织思想就是分层。不过基础服务提供者的层数相对分层服务提供者要少而已,理论上分层服务提供者可以有N层(不知道N是不是无限哦)

那么这些层和层之间是如何组织的呢?通过一个函数来把他们连接起来,这个函数就是
WSPStartup
下面是他的原型
int WSPStartup (
  WORD                     wVersionRequested,              
  LPWSPDATAW               lpWSPData,                
  LPWSAPROTOCOL_INFOW      lpProtocolInfo,  
  WSPUPCALLTABLE           UpcallTable,          
  LPWSPPROC_TABLE          lpProcTable          
);

这里比较重要的是第三个参数lpProtocolInfo和最后一个参数lpProcTable,lpProtocolInfo留到后面再讲,这和服务者的安装有关

现在我们看看ws2_32.dll里的API的,ws2_32里的API被应用程序调用后大部分都最终映射成了SPI里的30个函数,这30个函数都是用WSP开头的。注意,SPI里的30个函数是不能被应用程序直接调用的,而是应该由ws2_32.dll来调用。而LPWSPPROC_TABLE是一个表,里面保存了这30个函数的指针。我们通过调用下一层服务提供者的WSPStarup来得到下一层服务提供者的30个函数指针,同时我们也要EXPORT(SPI是一个DLL)这个函数,以便我们上一层的服务提供者来调用得到这30个函数指针。

例如
int WSPStartup (
  WORD                     wVersionRequested,              
  LPWSPDATAW               lpWSPData,                
  LPWSAPROTOCOL_INFOW      lpProtocolInfo,  
  WSPUPCALLTABLE           UpcallTable,          
  LPWSPPROC_TABLE          lpProcTable          
)
{
  LPWSPSTARUP WSPStarProc=GetProcAddress(LbHandle,"WSPStartup");//得到下一层服务提供者的WSPStatrup函数指针,记得要先LoadLibrary下一层服务提供者的DLL
  
  WSPStarProc(wVersionRequested,lpWSPData,lpProtocolInfo,UpcallTable,lpProcTable )
 
//记得要保留下一层原来的函数指针,有过HOOK API经验的朋友应该知道,就像要保存你HOOK 的API原来的地址一样


  WSPPROC_TABLE SystemProc=*lpProcTable;


//然后就可以设置自己的处理函数了,和HOOK API也差不多,比如
  lpProcTable->lpWSPSend=WSPSend;

  return 1;

然后我们实现自己的WSPSend

int WSPSend (
  SOCKET               s,                                                
  LPWSABUF               lpBuffers,                                      
  DWORD               dwBufferCount,                                     
  LPDWORD               lpNumberOfBytesSent,                             
  DWORD               dwFlags,                                           
  LPWSAOVERLAPPED        lpOverlapped,                            
  LPWSAOVERLAPPED_COMPLETION_ROUTINE   lpCompletionRoutine,  
  LPWSATHREADID          lpThreadId,                                
  LPINT               lpErrno                                            
)
{
  /*++
在这里我们用getsockname等函数和第一个参数可以得到这个套接字的端口和IP这些东西,还有buffer这些东西都可以在这里处理,实现IP过滤包过滤都可以实现,然后再调用下一层服务提供者的相应函数并返回
--*/
   return SystemProc.lpWSPSend(s,lpBuffers,dwBufferCount,lpNumberOfBytesSent,dwFlags,lpOverlapped,lpCompletionRoutine,lpThreaId,lpErrno);
 
}

当然,在具体的实现中没这么简单,对于WINSOCKET 中的复杂的I/O模型和重叠请求中,还要写回调函数等,现在就不讨论这些问题,有兴趣的自己查找资料

还有,用这个也可以实现sniffer功能,分析出比如POP3以及TELNET等明文传输的网络协议中你感兴趣的东西

数据都在LPWSABUF               lpBuffers,这个参数里


下面给出这个结构
该知道怎么解析包了吧?
typedef struct __WSABUF {
  u_long      len;
  char FAR    *buf;
} WSABUF, FAR * LPWSABUF;

-----------------
待续.....

本文地址:http://com.8s8s.com/it/it2149.htm