从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