一、前言
二、IMD中介層技術介紹
三、passthru常式分析
四、部分示範代碼
五、驅動編譯與安裝
六. 總結
一、前言
前段時間,在安全焦點上看到了TOo2y朋友寫的《基於SPI的資料報過濾原理與實現》,很是不錯。文章中提到的基於SPI的資料報攔截技術是在使用者級的。使用者級的攔截有其優勢,實現方便、便於移植、通用性強,但是,使用者級並不能得到所有的資料報。本文提到的基於IMD的資料報過濾則是屬於核心級的,它建立在網路驅動上面。
其實兩個月前就想寫這篇文章了,可惜這個技術是我一年前做的,好久不用,已經忘的差不多了,所以一直沒有寫。不過,近來正巧有個課題,牽扯到了中介層驅動。於是,重新拾起丟了有一年的DDK,啃了起來。這期間,真的要非常感謝linxder的幫忙,讓我少走了一些彎路,否則,我真的要從頭學起了。廢話不多說了,切入正題。
二、中介層驅動技術介紹
中介層驅動,英文為NDIS intermediate driver。
1)核心級網路驅動介紹
Microsoft Windows 2000支援三種基本的核心級網路驅動,這三層driver順序從下到上依次為:
1. Miniport NIC drivers:微連接埠網卡驅動,位於最底層,直接操縱網卡並且對高層驅動提供介面。
2. Intermediate drivers:IMD中介層驅動,這就是今天的主角,位於1和3之間,具體的作用下面就會介紹。
3. Protocol drivers:高層協議驅動,俗稱為TDI(傳輸驅動程式介面),高於前面兩層,直接面向使用者級,
為使用者提供網路服務,也就是絕大多數程式所用到的網路介面。
2)IMD驅動
IMD中介層,它的實質很簡單,最經典的描述莫過於下面的話:
An intermediate driver is typically layered over one or more NDIS NIC drivers and under a transport driver (possibly multilayered) that supports TDI at its upper edge.
An NDIS intermediate driver exports MiniportXxx functions at its upper edge and ProtocolXxx functions at its lower edge.(見DDK文檔)
中介層插入網卡和協議層之間,對上面的協議層表現為一個虛擬微連接埠網卡結構,而對下面的網卡則表現為一個協議層的結構。所以,無論是網卡接收並上傳的資料報,還是上層要下送至網卡發送的資料報,無一例外地要經過中介層。
3)IMD包過濾技術
前面我們已經看到,所有的資料報都要經過中介層,所以,我們可以在中介層加入我們想要過濾的資料報的特徵,實現基於中介層驅動的核心級包過濾。
這樣做的優勢非常明顯,首先,在驅動層級上做過濾,無須組包,速度快,效率自然就高;其次,所有的資料報無一例外,只要網卡上傳的資料報均可以截獲,避免了使用者級無法得到所有資料報的缺點。當然,世界上沒有完美的事情。IMD包過濾技術也存在其不可避免的缺點,與作業系統版本關係密切,與硬體聯絡大,可移植性低。我在調試這個驅動的時候,就碰到了無數次藍屏,無數次重啟動,進了幾次安全模式,甚至還為此重新安裝了一次系統。
正是由於上面的一些問題,現在市面上還沒有見到有廠家推出基於IMD的實用型防火牆,大部分都是在實驗室中的作品,或許真的是要做到通用性很難吧,不過還是希望能儘快見到這樣的產品面世。
三、passthru程式碼分析
到這裡,你或許已經非常想看看到底怎麼來實現基於IMD的包過濾防火牆了,不過,你肯定會遲疑,如果讓我們自己寫整個中介層驅動的話,是不是有些太艱難了啊?況且,我只是個搞網路安全的,我不是專門寫驅動的,讓我完成一個驅動程式,還要對上層協議偽裝成一個網卡,對下層偽裝成一個協議層,這不是要命嗎?
呵呵,其實微軟很不錯的,在提出這項技術之後,其DDK中附帶了一個中介層驅動的常式,就是passthru。passthru實現了一個中介層的準系統,對下表現為一個協議層的驅動,對上表現為一個虛擬網卡,安裝passthru驅動之後,你可以在硬體管理中的網卡中看到一個虛擬網卡。不過,passthru只是插入到網卡和上層協議中間,卻未做任何工作,也就是說,passthru只是讓所有的資料報原原本本地流經自己而已。我們要想實現中介層包過濾的功能,需要對passthru進行修改。
想想我們要實現的包過濾的功能,我們只需要在中介層接收到資料報的時候進行規則判斷就可以了,而在passthru中,接收資料報是用protocol.c檔案中的PtReceive和PtReceivePacket這兩個函數來實現的。根據微軟的解釋,微軟建議接收包用PtReceivePacket函數,因為可以得到更高的效率,然而,為了向下相容,也保留了PtReceive函數給老的網卡使用。所以,在一塊網卡上,只可能有一個函數在工作,這要取決於你的網卡型號了。巧的是,我的兩台機器的網卡應用的函數正巧不一樣。同樣是IBM的機器,一台P4 1.5G的機器的網卡是Realtek RTL8139(A) PCI Fast Ethernet Adapter,另外一台P4 2.0G的機器的網卡是Intel(R) PRO/100 VE Network Connection,其中Realtek網卡用的是PtReceive來接包,而Intel的網卡是用PtReceivePacket來接包。
現在我們知道了哪個函數負責接收資料報,那麼我們就可以對這個函數進行修改了。從相容和通用性考慮,我們需要對PtReceive和PtReceivePacket函數進行修改,其中加上我們需要判斷的規則進行過濾,下面就貼詳細的代碼了。
四、部分示範代碼
我們的目的是在調用接受資料報函數的時候能執行我們的過濾代碼,所以,我們要在函數代碼中添加我們自己的代碼,下面用過濾特定協議類型的資料報來做示範。
首先修改PtReceive,看一下protocol.c檔案中函數的代碼,代碼中用NdisGetReceivedPacket函數得到一個PNDIS_PACKET的結構Packet,資料報內容就存放在這個結構中的鏈表內。我們定義一個PUCHAR結構的pPacketContent,然後用下面的代碼獲得整個資料報的內容:
//---------------------------------------------------------
int PacketSize;
PUCHAR pPacketContent;
PUCHAR pBuf;
UINT BufLength;
MDL * pNext;
UINT i;
//把資料包內容從Packet拷貝到pPacketContent
NdisQueryPacket( Packet,NULL,NULL,NULL,&PacketSize);
Status= NdisAllocateMemory( &pPacketContent, 2000, 0,HighestAcceptableMax);
if (Status!=NDIS_STATUS_SUCCESS ) return Status;
NdisZeroMemory (pPacketContent, 2000);
NdisQueryBufferSafe(Packet->Private.Head, &pBuf, &BufLength, 32 );
NdisMoveMemory(pPacketContent, pBuf, BufLength);
i = BufLength;
pNext = Packet->Private.Head;
for(;;)
{
if(pNext == Packet->Private.Tail)
break;
pNext = pNext->Next; //指標後移
if(pNext == NULL)
break;
NdisQueryBufferSafe(pNext,&pBuf,&BufLength,32);
NdisMoveMemory(pPacketContent+i,pBuf,BufLength);
i+=BufLength;
}
//資料拷貝完畢
//---------------------------------------------------------
現在,我們已經在PtReceive函數中得到了資料報的內容,存放在pPacketContent中,資料報的格式你可以去查書。通常,在乙太網路中,得到的資料報大致是如下結構,以太幀頭14個位元組,放在pPacketContent[0]到pPacketContent[13]中,其中前六個位元組是目的MAC地址,然後六個位元組源MAC地址,然後兩個位元組是協議類型,通常的協議類型有0x08 0x00 ->IP,0x08 0x06 ->ARP,0x08 0x35 ->RARP,所以,可以通過pPacketContent[12]和pPacketContent[13]來判斷協議類型。如果是IP包,然後pPacketContent中存放的是IP頭,根據IP頭的格式,可以得到第23個位元組pPacketContent[23]表示傳輸層協議:1 ->ICMP,2 ->IGMP,6 ->TCP,17 ->UDP,剩下的就是資料報內容了。因為我們只是做示範,所以只要知道這幾個標誌性的就好了,其他的你可以根據你的需要擴充。我們通過pPacketContent中的內容可以做些規則,比如過濾ICMP包,我們只要比較pPacketContent[12]和pPacketContent[13]還有pPacketContent[23]這三個標誌位就可以了,如果不是ICMP包,那麼不做任何工作,如果匹配了,那就返回一個NDIS_STATUS_NOT_ACCEPTED,將包丟棄,釋放pPacketContent,就可以過濾ICMP包了,下面就是過濾規則的代碼。
//---------------------------------------------------------
//規則標誌位(1表示過濾,0表示允許存取,你可以通過改這個數值來配置規則)
UINT ICMP = 1; //ICMP資料報規則
UINT IGMP = 0; //IGMP資料報規則
UINT TCP = 0; //TCP資料報規則
UINT UDP = 0; //UDP資料報規則
//規則判斷
if (ICMP == 1)
{
if(((char *)pPacketContent)[12] == 8 &&
((char *)pPacketContent)[13] == 0 &&
((char *)pPacketContent)[23] == 1)
{
DbgPrint("ICMP被攔截!/n");
NdisFreeMemory(pPacketContent, 2000, 0);
return NDIS_STATUS_NOT_ACCEPTED;
}
}
if (IGMP == 1)
{
if(((char *)pPacketContent)[12] == 8 &&
((char *)pPacketContent)[13] == 0 &&
((char *)pPacketContent)[23] == 2)
{
DbgPrint("IGMP被攔截!/n");
NdisFreeMemory(pPacketContent, 2000, 0);
return NDIS_STATUS_NOT_ACCEPTED;
}
}
if (TCP == 1)
{
if(((char *)pPacketContent)[12] == 8 &&
((char *)pPacketContent)[13] == 0 &&
((char *)pPacketContent)[23] == 6)
{
DbgPrint("TCP被攔截!/n");
NdisFreeMemory(pPacketContent, 2000, 0);
return NDIS_STATUS_NOT_ACCEPTED;
}
}
if (UDP == 1)
{
if(((char *)pPacketContent)[12] == 8 &&
((char *)pPacketContent)[13] == 0 &&
((char *)pPacketContent)[23] == 17)
{
DbgPrint("UDP被攔截!/n");
NdisFreeMemory(pPacketContent, 2000, 0);
return NDIS_STATUS_NOT_ACCEPTED;
}
}
//規則判斷結束
//---------------------------------------------------------
到這裡,PtReceive函數已經修改完了,只要調用PtReceive函數接收資料報的時候,就可以執行我們的規則了,下面修改PtReceivePacket函數,其實上面的修改代碼內容都是一樣的,只不過PtReceivePacket函數跟PtReceive函數不太一樣,PtReceivePacket直接在入口參數中就得到了PNDIS_PACKET的結構Packet,存放了資料報的所有內容,所以,直接將上面的代碼粘到函數PtReceivePacket的代碼中就可以了。到現在,無論是網卡調用哪一個接收函數,都可以執行我們需要的規則。
其實,上面所定義的規則是很簡單的,因為只作為示範用,而我的課題嘛,嘿嘿,就不是那麼簡單了。其實在pPacketContent已經得到了所有的資料報內容,我們可以隨意擴充規則。比如,過濾指定IP、指定連接埠的資料報,至於具體應該設定哪個位置,你只要去找一本介紹資料報結構的書看一下就知道了,呵呵。
五、驅動編譯與安裝
前面談了那麼多,其實都是在說IMD包過濾的實現和驅動代碼,但是,我已經修改了代碼,怎麼編譯和安裝呢?
這一部分就解決驅動編譯和安裝的問題。我們用passthru的代碼,對接收資料報的部分做了修改。然後,我們用DDK內建的builder工具來編譯。我一般是用開始菜單中DDK程式組中的Free Build Environment,到passthru目錄下執行build -cz來編譯passthru,得到passthru.sys檔案,然後再到NTDDK/src/network/config/filter目錄下,執行build -cz來得到sfilter.dll,然後再加上passthru目錄下的netsf.inf和netsf_m.inf,一共四個檔案。這樣,驅動安裝所需要的檔案就全了。
開啟網路屬性,添加服務,找到passthru目錄下,安裝,彈出沒有數位簽章的警告,不理,繼續安裝,最後,你會發現,網路屬性中增加了一個名為Sample Filter的組件,同時,硬體管理器中的網卡下增加了一個Sample Filter Miniport的裝置。如果你已經到了這裡,並且系統沒有出現藍屏和死機,那麼恭喜你,你已經成功地安裝上了中介層驅動,並且已經發揮包過濾作用了。
六. 總結
難得我寫了那麼多,現在做一個總結吧。上文中的代碼,我只是用來做示範的,只用來說明基於IMD的包過濾防火牆原理與實現,其實需要做的工作還有很多,驅動上面碰到的問題會很多,並且常常伴隨著的是藍屏,死機,甚至要重裝系統。
你也許會問,那我每次改一下規則難道還要重新再安裝一遍驅動麼,況且,必須重啟動一次,舊的驅動才徹底卸載掉,多麻煩啊。呵呵,其實是中介層驅動是可以跟應用程式層的程式結合的。首先你要在驅動中指定好規則,應用程式層的程式通過DeviceIoControl傳遞指令到驅動中,可以控制驅動的規則標誌位。
精選驅動開發網(www.driverdevelop.com)論壇的ndis網路介面開發版,這裡是國內水平最高的地方,所有的與驅動有關的問題你都可以在那裡得到解決。
最後,附帶說一句,調試驅動的時候先做好處理系統後事的準備,對於出現的任何系統問題也好,硬碟問題也好,我不負任何責任,呵呵。
歡迎訪問我們團隊的網站:711網路安全小組 http://www.cpyy.net