從Windows軟件防火牆的誕生開始,這種安全防護產品就在跟隨著不斷深入的黑客病毒與反黑反毒之爭,不斷的進化與升級。從最早期的只能分析來源地址,端口號以及未經處理的報文原文的封包過濾防火牆,後來出現了能對不同的應用程序設置不同的訪問網絡權限的技術;近年來由ZoneAlarm等國外知名品牌牽頭,還開始流行了具有未知攻擊攔截能力的智能行為監控防火牆;最後,由於近來垃圾插件和流氓軟件的盛行,很多防火牆都在考慮給自己加上攔截流氓軟件的功能。綜上,Windows軟件防火牆從開始的時候單純的一個截包丟包,堵截IP和端口的工具,發展到了今天功能強大的整體性的安全套件。 接下來本文就對一個Windows軟件防火牆應當擁有的這些組件進行一個簡要的技術介紹。 封包過濾技術 封包過濾技術是最原始的防火牆所擁有的第一種功能。但是該功能簡單強大,直到現在都是任何一個防火牆必不可少的功能。 想要在網絡數據包到達應用程序之前攔截之,就要在系統的網絡協議棧上面安裝過濾鉤子。對Windows NT系列內核來說,可能安裝過濾鉤子的地方大致是這麼幾個,從高層到底層排序:SPI層(早期的天網防火牆 ),AFD層(資料缺乏,尚無例子),TDI層(不少國內牆),NDIS層(ZoneAlarm,Outpost等)。越位於高層,則產品開發難度越低,但是功能越弱,越容易被攻擊者所穿越。由於NDIS層的防火牆具有功能強大,不易被穿透等優點,近來各大防火牆廠商的趨勢是選擇NDIS層來做包過濾。 目前比較流行的NDIS鉤子技術有兩種。一種是掛接ndis.sys模塊的導出函數,從而能夠在每個ndis protocol注冊的時候截獲其注冊過程,從而替換其send(packets)handler和receive(packet)handler。這個方法的缺點是在第一次安全之後無法立刻生效,必須要重起一次,而且要禁用的話,也必須重起。 2004年12月的時候,上面的一名黑客發表了一篇著名的文章:“Hooking into NDIS and TDI, part 1。這篇文章本意是為rootkit作者們提供一種掛接底層驅動實現端口重用的方法,但是這篇文章揭示了一個全新的技術:通過動態的注冊ndis假協議,可以獲得ndis protocol的鏈表地址。得到這個地址之後就能不通過重起,就能替換並監控每個ndis protocol的send(packets)handler和receive(packet)handler,並且可以動態的卸載監控模塊不需要重起。在這篇文章出現之後,很多防火牆廠商都悄悄地對自己的產品進行了升級。目前的ZoneAlarm等產品就是使用這種技術,可以在安裝後即時發揮作用。這個例子更充分的體現了,黑客和反黑技術本來就是相輔相成的,本源同一的。 這裡給出一個尋找該鏈表頭的代碼例子: 該函數返回的NDIS_HANDLE就是鏈表頭地址。NDIS_HANDLE RegisterBogusNDISProtocol(void){ NTSTATUS Status = STATUS_SUCCESS; NDIS_HANDLE hBogusProtocol = NULL; NDIS_PROTOCOL_CHARACTERISTICS BogusProtocol; NDIS_STRING ProtocolName; NdisZeroMemory(&BogusProtocol,sizeof(NDIS_PROTOCOL_CHARACTERISTICS)); BogusProtocol.MajorNdisVersion = 0x04; BogusProtocol.MinorNdisVersion = 0x0; NdisInitUnicodeString(&ProtocolName,L"BogusProtocol"); BogusProtocol.Name = ProtocolName; BogusProtocol.ReceiveHandler = DummyNDISProtocolReceive; BogusProtocol.BindAdapterHandler = dummyptbindadapt; BogusProtocol.UnbindAdapterHandler = dummyptunbindadapt; NdisRegisterProtocol(&Status,&hBogusProtocol,&BogusProtocol, sizeof(NDIS_PROTOCOL_CHARACTERISTICS)); if(Status == STATUS_SUCCESS){ return hBogusProtocol;} else {#ifdef bydbg DbgPrint("ndishook:cannot register bogus protocol:%x\n",Status); DbgBreakPoint();#endif return NULL; }} 得到這個ndis protocol的鏈表後,遍歷表中的每一個ndis protocol,對於每一個ndis protocol,又各有一個鏈表,用來描述和該ndis protocol有聯系的所有ndis miniport和該ndis protocol綁定的狀態。每個這種狀態塊,叫做一個ndis open block。每個綁定的send(packets)handler和receive(packet)handler都在這個ndis open block裡面。struct _NDIS_OPEN_BLOCK{#ifdef __cplusplus NDIS_COMMON_OPEN_BLOCK NdisCommonOpenBlock;#else NDIS_COMMON_OPEN_BLOCK;#endif#if defined(NDIS_WRAPPER) // // The stuff below is for CO drivers/protocols. This part is not allocated for CL drivers. // struct _NDIS_OPEN_CO { .... };#endif};typedef struct _NDIS_COMMON_OPEN_BLOCK{ PVOID MacHandle; // needed for backward compatibility NDIS_HANDLE BindingHandle; // Miniport's open context PNDIS_MINIPORT_BLOCK MiniportHandle; // pointer to the miniport PNDIS_PROTOCOL_BLOCK ProtocolHandle; // pointer to our protocol NDIS_HANDLE ProtocolBindingContext;// context when calling ProtXX funcs PNDIS_OPEN_BLOCK MiniportNextOpen; // used by adapter's OpenQueue PNDIS_OPEN_BLOCK ProtocolNextOpen; // used by protocol's OpenQueue NDIS_HANDLE MiniportAdapterContext; // context for miniport BOOLEAN Reserved1; BOOLEAN Reserved2; BOOLEAN Reserved3; BOOLEAN Reserved4; PNDIS_STRING BindDeviceName; KSPIN_LOCK Reserved5; PNDIS_STRING RootDeviceName; // // These are referenced by the macros used by protocols to call. // All of the ones referenced by the macros are internal NDIS handlers for the miniports // union { SEND_HANDLER SendHandler; WAN_SEND_HANDLER WanSendHandler; }; TRANSFER_DATA_HANDLER TransferDataHandler; // // These are referenced internally by NDIS // SEND_COMPLETE_HANDLER SendCompleteHandler; TRANSFER_DATA_COMPLETE_HANDLER TransferDataCompleteHandler; RECEIVE_HANDLER ReceiveHandler; RECEIVE_COMPLETE_HANDLER ReceiveCompleteHandler; WAN_RECEIVE_HANDLER WanReceiveHandler; REQUEST_COMPLETE_HANDLER RequestCompleteHandler; // // NDIS 4.0 extensions // RECEIVE_PACKET_HANDLER ReceivePacketHandler; SEND_PACKETS_HANDLER SendPacketsHandler; // // More Cached Handlers // RESET_HANDLER ResetHandler; REQUEST_HANDLER RequestHandler; RESET_COMPLETE_HANDLER ResetCompleteHandler; STATUS_HANDLER StatusHandler; STATUS_COMPLETE_HANDLER StatusCompleteHandler; #if defined(NDIS_WRAPPER) ....#endif} NDIS_COMMON_OPEN_BLOCK; 需要處理的,是ndis open block裡面的SendHandler,ReceiveHandler,WanReceiveHandler,ReceivePacketHandler和SendPacketsHandler。 一定要注意的是,不同於很多文章中的描述,主要處理SendHandler和ReceiveHandler,正確的應該是主要處理ReceivePacketHandler和SendPacketsHandler,現在的主流網卡和系統驅動,都是使用後面兩者。
應用程序訪問網絡控制 以往的防火牆只能古板的允許或者禁止整個系統去訪問網絡上的目標,比如允許了系統可以訪問外網的http端口,就允許了所有進程,不能只控制IE等幾個進程有權這樣做。該技術的出現解決了這個問題,對每個陌生的進程都會詢問客戶是否允許訪問網絡,因此還有一定的查殺未知木馬病毒的能力。 由於NDIS裡面的那些send/receive handler全都是由tdi緩沖之後再調用的,,運行的上下文全都是kernel,並且不保存原先進行tdi操作的進程號,因此在封包過濾的NDIS鉤子層次無法取得進行操作的進程ID。想要解決應用程序訪問網絡控制的問題,就需要在tdi或者更高的層次上使用鉤子。一般來說,主流是使用tdi鉤子,在進程的網絡調用棧進行到tdi的TDI_CONNECT,TDI_LISTEN,TDI_RECEIVE,TDI_SET_EVENT_HANDLER等調用時,進行進程判斷和提示。 對於winsock的應用程序來說,最重要的是主動連接請求,TDI_CONNECT;接受連接請求,TDI_SET_EVENT_HANDLER中的TDI_EVENT_CONNECT。對於udp收發,還要處理TDI_SEND_DATAGRAM,TDI_RECEIVE_DATAGRAM和TDI_SET_EVENT_HANDLER中的TDI_EVENT_RECEIVE_DATAGRAM請求。這個時候,直接PsGetCurrentProcessId就可以得到進程號。 tdi鉤子有一個問題,就是對於TDI_SET_EVENT_HANDLER的hook,很可能不能及時發揮作用,必須要重起以後。由於不像ndis鉤子需要hook系統函數或者修改系統數據結構,tdi鉤子可以直接使用微軟提供的過濾器驅動程序接口,在安裝編寫上要比ndis鉤子簡單的多,IoAttachDeviceToDeviceStack就可以了。 給出一段detour的tdi的dispatch routine的代碼:NTSTATUS hook_disp(IN PDEVICE_OBJECT parampdrvob, IN PIRP irp){ .... case IRP_MJ_INTERNAL_DEVICE_CONTROL: switch(irpsp->MinorFunction) { ///原來想得要監控的幾個似乎afd並不使用,而是用set event handler case TDI_LISTEN:#ifdef bydbg DbgPrint("bytdiflt:TDI_LISTEN traped.should caused by kmd other than AF