一個簡單的tcp filter的例子

一個簡單的tcp filter的例子

來源:華中白雲黃鶴BBS
作者:huyuguang


    這兩天版面日漸蕭條,我只好硬着頭皮把我自己的一個尚在開發,非常不成熟
的東西拿出來了。
    前面我曾經就網絡加密這個議題討論過,那個時候我提出的方法是imd,
9x下就是利用hook_device_service,用這些方法的一個好處就是,接收數據
比較低層,直接得到的就是mac數據,而且可以知道是哪塊網卡上來的,而且,
可以加密各種數據幀,ipx,ip什麼的都不在話下。因爲我所遇到的加密環境和要求,
幾乎都是要求對tcp加密,在這種情況下,做在imd上的話,顯然會降低ipx或者icmp
之類的非加密數據包的效率。因此以前我就考慮在tcp 上做一個filter,直接加密tcp
的數據。
    正好在mark的站點上新出來一個小工具,叫做tdimon,工具很好,可以看到各個進程
的通訊狀態。可惜的是1.沒有源代碼,2.free版本不能得到數據內容。有這兩點限制,這個
工具就只能當作玩具。正好那個時候我比較閒,就花了點時間做了一個類似的工具。
關於FILTER,art baker的書裏說得很詳細,我做的時候基本上也就是根據這本書上來的,
做tcp的filter,和做別的設備的filter,沒什麼區別,但是不同是由於不知道tcp是如何
和別的設備(tdi clinet)通訊的,所以必須仔細看tdi的規範。
    我先描述一下2000/nt下的tcp/ip協議的一些情況。2000/nt下,ip,tcp,udp是在
一個驅動程序裏實現的,叫做tcp.sys,這個驅動程序創建了3個設備,就是ip,tcp,udp。
    首先描述一下driverentry。首先當然是iocreatedevice,用file_device_unknown,
因爲tcp設備就是用的這個參數。代碼如下:
    RtlInitUnicodeString( &usDeviceName, FILTER_NAME );
    status = IoCreateDevice( DriverObject,
                            sizeof(DEVICE_EXTENSION),
                            &usDeviceName,
                            FILE_DEVICE_UNKNOWN,
                            0,
                            FALSE,
                            &pDevObj );
然後就調用iogetdeviceobjectpointer來得到tcp設備的指針。
代碼如下:
    RtlInitUnicodeString( &usTargetName, TARGET_NAME );
    status = IoGetDeviceObjectPointer( &usTargetName,
                                      FILE_ALL_ACCESS,
                                      &pTargetFileObj,
                                      &pTargetDevObj );
注意TARGET_NAME是大小寫區分的,我是用#define TARGET_NAME L"//Device//Tcp",
不能寫成#define TARGET_NAME L"//Device//tcp"。
然後我們就開始調用IoAttachDeviceToDevieStatck插入到tcp設備。
調用完成後,我們要讓我們的設備表現的和tcp一樣,於是把它的所有
特性都從tcpobj複製(pdevobj->xxx=ptcpobj->xxx)。
再掃描他tcpdriverobject的majorfunction,我們的driver必須都
支持。最後設置driverunload,因爲爲了方便調試,我們必須寫一個
unload。

前面已經講過driverentry了,經過driverentry,所有的原來應該發送到
tcp設備的irp現在都發送到我們的設備的處理函數了,如果什麼事情都不做,
那麼可以簡單的調用iocalldriver把這個irp發到tcp設備去讓它處理。
當然,我們是要做些事情的,於是代碼如下:
    UCHAR MajorFunction,MinorFunction;
    PDEVICE_EXTENSION  pDevExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
    PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( Irp );
    MajorFunction = pIrpStack->MajorFunction;
    MinorFunction = pIrpStack->MinorFunction;
    file://DBGPRINT( ...)
    ParseIrp(Irp);
    IoCopyCurrentIrpStackLocationToNext( Irp );

    IoSetCompletionRoutine( Irp,
                            CompletionRoutine,
                            NULL,   // context
                            TRUE,   // InvokeOnSuccess
                            TRUE,   // InvokeOnError
                            TRUE ); // InvokeOnCancel
   
    return IoCallDriver( pDevExt->TargetDevObj, Irp);

代碼很簡單,除了parseirp之外,其他都是例行公事。對於tcp設備的前期處理
就在這個函數裏。後期處理的函數可以放在completionroution裏。

好了,我們已經得到了發向tcp設備的所有irp了,這個時候我們的任務就是要了解
tcp設備到底是如何工作的.


我們的任務是要得到本機發送出去的tcp數據和接受的tcp數據。當然前提是發送數據
通過tcp設備,如果某些應用程序用packet.sys之類的協議driver,直接發送tcp數據,
這種情況下數據不經過tcp設備,filter也就無從得到了。
爲了瞭解如何從發向tcp設備的irp中得到信息,首先我先描述一下tdi client是如何
與tcp通訊以及tdi client一般是如何工作的。
driverstdio裏面有好幾個例子都是關於tdi client的,但是這些例子都是基於它自己
的類庫的,不過這些例子功能都很強大,其中一個usb+web的溫度計的創意簡直讓我目瞪口呆。
因爲我也做過pci溫度計的driver,但是我從來就沒有想到還可以加上一個web server在裏面。
因爲driverstdio裏的例子太複雜,我在這裏簡單描述一下,給一個小例子。
因爲發送過程較爲簡單,先描述發送。
首先調用pIrp = TdiBuildInternalDeviceControlIrp (
                     TDI_SEND_DATAGRAM,                              // sub function
                     pDeviceObject,                                  // pointer to device object
                     pTransportObject,                               // pointer to udp object
                     NULL,                                           // pointer to event
                     NULL );                                         // pointer to return buffer
分配一塊irp,然後調用
        TdiBuildSendDatagram (
                           pIrp,                                     // pointer to irp
                           pDeviceObject,                            // pointer to device object
                           pTransportObject,                         // pointer to file object
                           NULL,                                     // completion routine
                           NULL,                                     // completion context
                           pMdl,                                     // pointer to data
                           dBufferSize,                              // size of data
                           pConnectInfo );                           // connection information
不用我說也應該知道,數據是放在一個buf裏面,調用這個函數之前,要先構件一個mdl,把這個buf
放進去。
然後...irp已經好了,只要IoCallDriver(pUDPObject,PIrp)就行了...

上面用的是UDP的例子(Datagram),但是tcp也是一樣,雖然函數有點差別,但是也是大同小異(TdiBuildSend)。

爲了搞清楚上面的兩個函數到底做了些什麼(到底構建的irp是什麼樣子),沒有必要去跟蹤,實際上,tdibuildxxx
都不過是一個宏,你可以在tdikrnl.h裏找到。打開tdikrnl.h看看,發現每個宏裏都有這麼一句:
        _IRPSP->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;             
於是我們知道tdi client就是通過majorfunction=IRP_MJ_INTERNAL_DEVICE_CONTROL,minorfunction=send/recv/...
和tcp通訊的。這就使得我們確信,這種方法是可行的。(不是直接調用tdi函數,而是發irp)

接收的過程較爲複雜(tdi client的選擇較多,因此要考慮各種情況)
開始的時候,我在ddk document裏看到這麼一個宏:tdibuildreceive,我想ok,
和send一樣,我當時想的很簡單,以爲tdi client要接受數據了,它就向tcp發一個
irp,然後返回pending,當有數據來的時候,tcp處理完這個irp,然後tdi client只要在這個irp的
irp_complete裏處理得到的數據就行了。這的確是tdi client的一個選擇,但是
當我試驗的時候,我發現至少wsock不是這樣做的,因爲我打開了一個ie,瀏覽了一個網頁,
但是我發現我發出的數據都很好的捕獲到了,但是接收的數據一個也沒有,而且事實上,
tcp似乎就沒有受到minorfunction=tdi_receive的irp。我因爲這件事苦惱了一段時間,
後來我仔細的讀了讀ddk document,發現tdi client還有第2種選擇,首先向tcp發送一個
irp,minorfunction=tdi_sethandler,設置一些回調函數,然後當事情發生的時候,tcp
就會調用這些回調函數,這些函數名字是clientEventxxx。這個過程可以仔細看ddk document,
看TdiBuildSetEventHandler,就知道哪些過程可以用這個方法。receive也是其中的一個,於是
我們的方法得到了。首先我們得到了irp,如果是set_eventhandler,那麼就修改tdi client
向tcp設置的回調函數的入口,把它指向我們自己寫的一個函數,同時保留原來的入口,
然後在我們自己寫的函數裏裏調用它。
代碼如下:
       PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( Irp );
    MajorFunction = pIrpStack->MajorFunction;
    MinorFunction = pIrpStack->MinorFunction;
    FileObject = pIrpStack->FileObject ;
       ....
    switch(MajorFunction)
    {
        ...
        case IRP_MJ_INTERNAL_DEVICE_CONTROL  :
        {
            switch(MinorFunction)
            {
                ...
                case TDI_SET_EVENT_HANDLER:
                pRequestSetEvent = (PTDI_REQUEST_KERNEL_SET_EVENT)(&(pIrpStack->                        Parameters.DeviceIoControl)) ;
                EventType=pRequestSetEvent->EventType;
                EventHandler=pRequestSetEvent->EventHandler;
                   EventContext=pRequestSetEvent->EventContext;
                ...   
                switch(EventType)
                {
                    ...   
                    case TDI_EVENT_RECEIVE :
                        pRequestSetEvent->EventHandler = OurClientEventReceive;
                        g_EventReceive = EventHandler;
                        break;
                    ...
值得注意的是,就算是set_eventhandler,也不一定就是tdi_event_receive裏接受數據,
這個tdi_client的選擇很多,還有ClientEventChainedReceive 什麼的,詳情可看ddk,
winsock似乎都是沒有用過這個。不過這個不影響,我們可以把ddk裏面所有可能的情況
全部列出來,一一判斷,然後分別處理。對於clienteventreceive,這裏還有幾句話要說
清楚,並不是每次調用這個函數就得到了數據,這還要根據ReceiveFlags參數,tdi client
要根據這個參數決定是否要發tdi_receive irp得到數據。於是我們的處理函數就要判斷這個
參數,以便做出相應的處理,我自己的代碼在實際的環境中運行了一段時間,似乎沒有問題,但是
我也不敢說,對於各種情況我都考慮到了。

 

 


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章