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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM