WINDOWS系统下的端口复用(我来补充FsContext)

by shadow3 

转:http://hi.baidu.com/hnxyy/blog/item/c65aa86ebd56ceda80cb4a46.html

前段时间在研究TDI和NDIS,发现在WINDOWS下复用端口并非很麻烦的事情,如果你对TDI CLIENT比较熟悉的话,会发现这其实很容易,之前有很多种方法都可以复用端口,但是都会有或多或少的一些问题,例如hook winsock函数,他需要把每个进程里的函数都hook,才能保证可以复用端口,而且这种方法对于win2k3的IIS6不通用,因为iis6有内核驱动http.sys来处理连接管理和数据接收等,当然后来还有NDIS上的hook,用他来接受所有的数据包,然后自己在重新组包发出去,这种方法当然好了,但是缺点是太麻烦,要自己组包发,而且稳定起来也不是很容易,有人会说不是还有TDI的 hook 吗,哪个方法好是好,但是在第一次运行的时候,其实并不能得到所有端口的数据的,因为其他的地址对象已经注册过recv事件处理了,我们能做的是乞求下一个应用层的程序使用socket的时候,我来得到 hook 他的注册的 TDI_EVENT_CONNECT,TDI_EVENT_RECEIVE等事件处理例程。
经过前段时间的TDI CLIENT的编写,发现一种不错的方法来实现端口的复用。下面我来大概介绍一下:

使用过socket写网络程序的人都知道应用层使用bind函数来绑定一个端口,使用listen去监听端口,使用accept函数的返回的socket去表示远程计算机,而核心层呢,他们又是怎么做的呢,首先创建一个地址对象,如果需要bind一个端口的话,在创建地址对象的时候指定一个需要绑定的端口,接着下发一个IRP去注册TDI_EVENT_CONNECT的响应事件的回调函数,这就相当于我们的listen,接下来我们需要创建一个连接端点对象(将来我们就使用这个连接对象来和我们的远程端点进行数据交换)并将它和我们的地址对象进行关联,这一步,我们的应用层没有对应的函数与之对应,然后我们在TDI_EVENT_CONNECT事件的回调函数中下发TDI_ACCEPT标志的IRP来完成我们的接收工作,这一步相当于我们的accept,等我们成功接收到远程计算机的连接后,我们就可以下发标志为TDI_SEND的IRP包将我们的数据发送给远程计算机了,如果我们之前注册了TDI_EVENT_RECEIVE等事件的话,我们就可以在我们TDI_EVENT_RECEIVE事件的回调函数中得到远程计算机发送给我们的数据了,如果没有注册,我们可以自己下发TDI_RECEIVE标志的IRP进行数据接收(如果已经注册了TDI_EVENT_RECEIVE事件的回调函数,那么我们下发IRP去接收数据,回调函数将不会被调用)。现在看看上面说的好象还没有关联到题目中,不过要是你写 TDI_CLIENT 就会知道,我们发送数据和接收数据时,我们的IRP所使用的文件对象都是之前创建的连接端点,至于为什么这么做,有兴趣可以去看看DDK里的说明。

前面我们聊了聊kernel中端对端进行接受连接和数据发送的过程,而发送和接收数据的过程中,最关键的东西就是我们创建的连接端点对象,因为我们发送数据和接收数据都离不开他。我们假设一下,如果我们拥有了这个连接端点对象,我们是不是也就可以在这条连接上发送或者数据了,经过实验说明,这样是可以的,下面我们在看看如何得到这个连接对象。

ZwQuerySystemInformation是一个WINDOWS未公开的一个native api函数,它可以帮助我们查询很多系统信息,例如我们系统中所有的对象的句柄信息,当然这包括我们的连接端点对象的句柄,很好,现在我们可以使用这个函数查询所有的对象的信息,然后可以使用ZwQueryObject函数来判断是否是一个和\DEVICE\TCP有关系的文件对象,对于和\Device\Tcp相关的文件对象有3中,分别是地址对象,连接端点对象,信道对象(他们其实只是windows基本对象中的文件对象而已),这里我们只需要连接对象就可以了,在这里,我们来看看FILE_OBJECT的结构

kd> dt nt!_FILE_OBJECT
  +0x000 Type         : Int2B
  +0x002 Size         : Int2B
  +0x004 DeviceObject   : Ptr32 _DEVICE_OBJECT
  +0x008 Vpb         : Ptr32 _VPB
  +0x00c FsContext     : Ptr32 Void
  +0x010 FsContext2     : Ptr32 Void
  +0x014 SectionObjectPointer : Ptr32 _SECTION_OBJECT_POINTERS
  +0x018 PrivateCacheMap : Ptr32 Void
  +0x01c FinalStatus     : Int4B
  +0x020 RelatedFileObject : Ptr32 _FILE_OBJECT
  +0x024 LockOperation   : UChar
  +0x025 DeletePending   : UChar
  +0x026 ReadAccess     : UChar
  +0x027 WriteAccess     : UChar
  +0x028 DeleteAccess   : UChar
  +0x029 SharedRead     : UChar
  +0x02a SharedWrite     : UChar
  +0x02b SharedDelete   : UChar
  +0x02c Flags         : Uint4B
  +0x030 FileName       : _UNICODE_STRING
  +0x038 CurrentByteOffset : _LARGE_INTEGER
  +0x040 Waiters       : Uint4B
  +0x044 Busy         : Uint4B
  +0x048 LastLock       : Ptr32 Void
  +0x04c Lock         : _KEVENT
  +0x05c Event         : _KEVENT
  +0x06c CompletionContext : Ptr32 _IO_COMPLETION_CONTEXT

经过实验分析,发现MICROSOFT是通过其中FsContext2字段来区分这3种对象,在这个字段中放的是一个序号,当FsContext2 == 2时,这个文件对象就是连接端点对象,哈哈,到这一步,我已经很兴奋了,因为我们已经离我们的目标越来越近了。不过这个时候我们又遇到一个问题,就是这样得到的连接对象会有很多,那我们怎么来区别这些连接端点对象呢,我的做法是使用SRC IP,SRC PORT,DEST IP,DEST PORT,我们如果能得到连接对象的源IP,源端口,目标IP,目标端口,这样我们就可以清楚的知道哪个连接端口地址对象才是我们需要的对象。那怎么来得到这些数据呢?看了很多资料也没找到一个比较好的办法,最后只好自己动手了,看看MS自己是怎么来得到这些数据的(协议栈一定会用到的),通过TCPIP.sys的分析,终于有了答案,原来MS自己维护了很多数据结构,其中有一个数据结构中包含有这些信息,而得到数据结构的方法就是从FsContext里,从2k-2k3,FsContext中存放了一些有用的数据(说实话除了第一个双字,别的我也不知道是啥东西),当然其中最有用的,就是FsContext指向的内存的第一个双字,这个双字中存放着一个索引,MS根据自己的索引算法来得到一个数据结构的首地址,而这个数据结构中就有我们需要的数据,IP地址和端口。索引算法我就不公布了,大家自己也分析一下,算法不难比较简单,提示一下算法中使用ConnTable(一个TCPIP.SYS的一个核心表)变量。

好了,根据上面的做法,我们成功定位到了一个连接端点对象,那么,我们在这个对象上进行收发包吧,GAME OVER。

注:该方法是否真的稳定使用还需要大家的指正和研究,我这里写了个POC版的kernel door,在我的win2k,winxp,win2k3上都可以良好运行。该方法的优点在于不需要太多的HOOK和组包,使用WINDOWS自己的协议栈进行数据交换,缺点也有很多,其中不对之处还请大家多多指正

 

============================================================================================

 

关于上面的File_object的fscontext秘密我来补充(在TCPCreate函数中找出来的)

fscontext是一个_TCP_CONTEXT结构(这个结构对应reactos中的TRANSPORT_CONTEXT)

 

当fscontext2=1时,_TCP_CONTEXT第一个字节的内容是一个addrobj结构指针(也就是reactOS中提到的AddressFile,由TdiOpenAddress返回)),

 

 

当fscontext2=2时,_TCP_CONTEXT第一个字节的内容是一个connID,(由TdiOpenConnection返回)

,这个tcpcontext是AfdConnect-afd!AfdCreateConnection->tcpcreate的调用创建的

 

 

当fscontext2=3时,_TCP_CONTEXT第一个字节的内容是 0?

 

PS:有点要说明下,发现建立好连接后,AddrObj的目标IP和端口都没填写,只填写了本地IP和端口

afd注册的接收回调函数是在tcb结构中

 

 

 

 

 

(fscontext和fscontext2的填写是在TCPCreate函数中完成的)

来看下这个ConnID有什么用

 

 

.text:00053544 8B D7             mov     edx, edi

.text:00053546 81 CA 00 00 01 00 or      edx, offset __ImageBase

.text:0005354C C1 E2 08          shl     edx, 8

.text:0005354F 89 56 44          mov     [esi+TCPConn.tc_connid], edx  这里是ID

.text:00053552 88 4E 14          mov     [esi+TCPConn.tc_inst], cl

.text:00053555 FF 43 18          inc     [ebx+TCPConnBlock.cb_conninst]

.text:00053558 8B 0D 10 0A 07 00 mov     ecx, _ConnTable

.text:0005355E 89 1C B9          mov     [ecx+edi*4], ebx

 

 

知道edx=ID就可以往上推得出edi,接着可以从这句 mov     [ecx+edi*4], ebx 中得出ebx,也就是TCPConnBlock,而TCPConnBlock的 +1C 处的cb_conn就是TCPConn结构(TCPConn +40处的tc_ConnBlock也可以反推得出TCPConnBlock)

 

 TCPConn +18处是afd!_AFD_CONNECTION结构

 

TdiAssociateAddress 可以通过tcpip!GetConnFromConnID()使用那个ID找出 tcpconn结构

接下来如果tcpconn的tc_ao字段为空,就会把AddrObj 填写到tcpconn的tc_ao字段,那个AddrObj是在fscontext中取的

 

 

tcpip!TCPRcv一些函数的调用是

 

f896fd54 b3be4ffa tcpip!DeliverToUser+0xae

f896fdfc b3c09fa6 tcpip!IPRcvPacket+0x5c8

f896fe3c b3c0c508 tcpip!ARPRcvIndicationNew+0x1f3

f896fe78 f8217102 tcpip!ARPRcvPacket+0x66

f896fed4 f7e56338 NDIS!ethFilterDprIndicateReceivePacket+0x4d0

f896ff08 f8216fa3 psched!ClReceivePacket+0x27e

其中DeliverToUser里面会调用FindUserRcv,根据FindUserRcv参数中提供的IP头buffer,recv buffer主要是protocol找出接收回调。如TCP的好像就是返回

eax=tcpip!TCPRcv(驱动在初始化的时候根据不同的协议就注册了不同的回调)

接着就call eax.

 ====================================================================================

 

 

xt:B3C25182

.text:B3C25182 55                push    ebp

.text:B3C25183 8B EC             mov     ebp, esp

.text:B3C25185 53                push    ebx

.text:B3C25186 8B 5D 08          mov     ebx, [ebp+ConnID]

.text:B3C25189 8B C3             mov     eax, ebx

.text:B3C2518B 8B CB             mov     ecx, ebx

.text:B3C2518D 56                push    esi

.text:B3C2518E C1 E8 08          shr     eax, 8

.text:B3C25191 57                push    edi

.text:B3C25192 81 E1 FF 00 00 00 and     ecx, 0FFh

.text:B3C25198 25 FF FF 00 00    and     eax, 0FFFFh           ; 高字节用于在conntable中取出一个表基地址 Base1

.text:B3C2519D C1 EB 18          shr     ebx, 18h              ; 简单算法是connID的低一个字节是index

.text:B3C251A0 33 FF             xor     edi, edi              ; 接着[base1+1c+index*4]则可得出tcpconn结构

.text:B3C251A2 81 F9 00 01 00 00 cmp     ecx, 100h

.text:B3C251A8 0F 83 83 00 00 00 jnb     loc_B3C25231

.text:B3C251AE 3B 05 0C 2A C4 B3 cmp     eax, _MaxAllocatedConnBlocks

.text:B3C251B4 73 7B             jnb     short loc_B3C25231

.text:B3C251B6 8B 15 10 2A C4 B3 mov     edx, _ConnTable

.text:B3C251BC 8B 34 82          mov     esi, [edx+eax*4]

.text:B3C251BF 85 F6             test    esi, esi

.text:B3C251C1 74 70             jz      short loc_B3C25233

.text:B3C251C3 8B 7C 8E 1C       mov     edi, [esi+ecx*4+1Ch]

.text:B3C251C7 85 FF             test    edi, edi

.text:B3C251C9 74 68             jz      short loc_B3C25233

posted @ 2012-06-08 22:22  kkindof  阅读(6043)  评论(0编辑  收藏  举报