关闭

关闭

关闭

封号提示

内容

首页 Windows TDI过滤驱动开发

Windows TDI过滤驱动开发.pdf

Windows TDI过滤驱动开发

drui118 2010-12-18 评分 0 浏览量 0 0 0 0 暂无简介 简介 举报

简介:本文档为《Windows TDI过滤驱动开发pdf》,可适用于IT/计算机领域,主题内容包含WindowsTDI过滤驱动开发版权所有  本文作者是楚狂人代码来源于开源工程tdifw与DDK的例子  有问题欢迎与我联系讨论。  mail:mf符等。

WindowsTDI过滤驱动开发版权所有  本文作者是楚狂人代码来源于开源工程tdifw与DDK的例子  有问题欢迎与我联系讨论。  mail:mfctanwencom  QQ:  msn:walledriverhotmailcom       WindowsTDI过滤驱动开发             目 录()TDI概要()准备工作()TDI设备与驱动入手()绑定设备()简单的处理请求()基础过滤框架()主要过滤的请求类型()CREATE的过滤()准备解析ip地址与端口()获取生成的IP地址和端口()连接终端的生成与相关信息的保存()TDIASSOCIATEADDRESS的过滤()TDICONNECT的过滤()TDISEND,TDIRECEIVE,TDISENDDATAGRAM,TDIRECEIVEDATAGRAM()设置事件()TDIEVENTCONNECT类型的设置事件的过滤()一个传说中的问题()收尾与清理的工作(第/页)WindowsTDI过滤驱动开发()TDI概要  最早出现的网络驱动应该是网卡驱动这是Windows的理所当然的需求为了进一步分割应用程序的网络数据传输与下层协议直到下层硬件的关系又出现了协议驱动后来微软和硬件商联合制定了NDIS标准作为从硬件到协议的内核驱动程序的调用接口标准而协议驱动与应用层的API之间则出现了TDI接口。  最近国内安全软件的开发兴起网络驱动的开发在其中有不少的应用如果我们学习TDI接口的话可能有以下一些目的:  自己要开发协议驱动向上提供TDI接口这种可能性存在但不广泛。  我们想自己调用TDI接口来进行网络数据传输意义不大。  我们对TDI进行协议层过滤开发防火墙或类似安全监控软件这种应用还是比较多的。(第/页)WindowsTDI过滤驱动开发()准备工作  为了开始研究网络驱动开发建议你做如下的准备:  安装VCnet、VC或者更老的版本也可以编译驱动程序但是本文提供的工程都是VCnet工程如果要看范例代码你可能必须安装VCnet。  然后需要安装DDK另外TDI过滤在DDK中并没有现成的例子(但是提供TDI接口的NDIS网络驱动应该是有的)。直接研究TDI接口是件痛苦的事情这套接口秉承了微软公司各类接口的特点:臃肿而且复杂。幸运的是有用TDI过滤写成的本地防火墙的开源代码可以研究。  你必须自己学习DDK的编译方法和一般的应用程序稍有不同。  tdifw是一个基于TDI过滤的开源本地防火墙有兴趣的读者可以在这里下载代码:    http:tdifwsourceforgenet  这个防火墙有很多额外的代码我把它进行了精简和修改附在本文的范例代码中提供下载工作空间为tdifwsln。可以打开直接编译。  此外作为驱动开发者你还必须准备一些工具:  驱动加载工具编译好一个驱动你可以动态加载它(NDIS网络驱动稍微有些不同以后再详细介绍)。这样你需要一个工具加载它我推荐installerexe。遗憾的是网络上有很多软件叫做installer我只能说这个installerexe的图标是一个黄色安全帽。  输出查看工具推荐DbgViewexe你可以在以下地址下载:    http:wwwsysinternalscomUtilitiesDebugViewhtml  给驱动程序配上界面绝对不是一件简单的工作所以需要一个输出查看工具这样至少在程序中可以print一些东西用这个工具可以看到在没法安装调试工具的情况下print能起巨大的作用。我有softice老安装失败最后全部依靠print解决bug的经历。  调试工具推荐windbg和softice。  windbg可以免费下载地址为http:wwwmicrosoftcomwhdcdevtoolsdebuggingdefaultmspx  windbg和softice的使用都需要专门的章节介绍本文略过所以有兴趣的读者请在驱动开发网上寻找相关的介绍。  没有安装并不会使用调试工具的情况下你也可以继续阅读并试用本文附带的代码。(第/页)WindowsTDI过滤驱动开发()TDI设备与驱动入手  TDI是一组接口的名字协议驱动本身都是NDIS协议驱动。但是其上提供TDI接口Windows的上层网络组件调用这些接口来使用协议驱动。这些接口是可以被过滤的下面以一个TDI过滤驱动为例。  TDI过滤相比NDIS过滤的一大好处是TDI离应用层比较近容易得到应用程序的相关信息。比如一个连接建立你可以获取打开这个连接的PID也就得知了打开这个连接的应用程序。而不足之处则是安全性若我想写一个木马来绕过TDI接口我可以不调用一般网络API来避免调用TDI接口不过NDIS过滤虽然相对保险绝对的安全依然是不可能的。  提供TDI接口的Windows协议驱动将在Windows内核中生成所谓的TDI设备。设备是有路径和名字的比较著名的设备有以下几个:  "DeviceTcp"对应TCP协议。  "DeviceUdp"对应UDP协议。  "DeviceRawIp"对应原始IP包。  如果你安装了TCPIP之外的协议应该还有更多既然我们要过滤TDI接口那么首先就是生成我们自己的设备来绑定这些设备。  这就是过滤的原理。如果我们用自己的设备绑定了原有的设备那么Windows将把本来给原设备的请求给我们的新设备。从而可以过滤他们。  驱动开发的老手对生成设备和绑定设备再熟悉不过但是没有开发过驱动的朋友可能有些困惑我的建议是没有必要一开始就试图了解太多概念只要了解代码如何写就可以了。  我们建立c文件包含如下文件:  #include<ntddkh>  #include<tdikrnlh>  这两个文件都是ddk的头文件这就是为何你得安装DDK然后我们开始写一个函数DriverEntry  这个函数相当与一般Windows应用程序之WinMain原型如下:NTSTATUSDriverEntry(INPDRIVEROBJECTtheDriverObject,INPUNICODESTRINGtheRegistryPath)IN是一个无任何内容的宏代表后面的参数为输入参数有些用来返回结果的指针前面会带OUT仅仅起提示作用最简单的写法为:NTSTATUSDriverEntry(INPDRIVEROBJECTtheDriverObject,INPUNICODESTRINGtheRegistryPath){returnSTATUSUNSUCCESSFUL}这个驱动已经可以编译了只是无法加载总是返回STATUSUNSUCCESSFUL错误。(第/页)WindowsTDI过滤驱动开发()绑定设备下面我们在这个过程中完成绑定主要的TDI设备的任务下面这个函数来自tdifw工程你可以在tdifwc中找到这些代码。这个函数生成一个设备来绑定一个已知名字的设备。NTSTATUScnadevice(PDRIVEROBJECTDriverObject,PDEVICEOBJECT*fltobj,PDEVICEOBJECT*oldobj,wchart*devname){NTSTATUSstatusUNICODESTRINGstr生成自己的新设备status=IoCreateDevice(DriverObject,,,FILEDEVICEUNKNOWN,,TRUE,fltobj)if(status!=STATUSSUCCESS){KdPrint(("tdifwcnadevice:IoCreateDevice(S):xxn",devname,status))returnstatus}设置设备IO方式为直接IO(*fltobj)>Flags|=DODIRECTIO将要绑定的设备名初始化为一个Unicode字符串RtlInitUnicodeString(str,devname)绑定这个设备status=IoAttachDevice(*fltobj,str,oldobj)if(status!=STATUSSUCCESS){KdPrint(("tdifwDriverEntry:IoAttachDevice(S):xxn",devname,status))returnstatus}KdPrint(("tdifwDriverEntry:Sfileobj:xxn",devname,*fltobj))returnSTATUSSUCCESS(第/页)WindowsTDI过滤驱动开发}有这么一些要点要注意的:常常使用Unicode字符串而非一般的字符串Unicode字符串常常用RtlXxxUnicodeString系列的函数操作你应该翻阅DDK帮助来熟悉它们。用KdPrint来代替printf输出信息这些信息可以被你的工具(比如DbgViewexe)看到。IoCreateDevice()和IoAttachDevice()打开一个设备然后对它进行绑定查阅帮助文档了解它们的用法吧。()简单的处理请求如果一个驱动生成了设备那么则必须设置处理Windows发给这些设备的请求的分发处理函数。作为过滤你可以有很多选择。比如直接调用真实的设备的处理过程或者自己处理。在前面的DriverEntry中添加:for(i=i<IRPMJMAXIMUMFUNCTIONi)theDriverObject>MajorFunctioni=DeviceDispatch这样一来所有的请求都发到DeviceDispatch这个函数这个函数成为唯一的分发函数。为了让我们的程序能快速的继续开发下去我们写一个直接调用真实设备的处理过程的DeviceDispatch:NTSTATUSDeviceDispatch(INPDEVICEOBJECTDeviceObject,INPIRPirp){PDEVICEOBJECTolddevobj=getoriginaldevobj(devobj,)if(olddevobj!=){如果能找到原设备,则发到原设备IoSkipCurrentIrpStackLocation(irp)status=IoCallDriver(olddevobj,irp)}else{如果不能找到,则返回失败status=irp>IoStatusStatus=STATUSINVALIDPARAMETERIoCompleteRequest(irp,IONOINCREMENT)}(第/页)WindowsTDI过滤驱动开发returnstatus}另外一个疑问是如何获得原有真实设备getoriginaldevobj的代码很简单:PDEVICEOBJECTgetoriginaldevobj(PDEVICEOBJECTfltdevobj,int*proto){PDEVICEOBJECTresultintipprotoif(fltdevobj==gtcpfltobj){result=gtcpoldobjipproto=IPPROTOTCP}elseif(fltdevobj==gudpfltobj){result=gudpoldobjipproto=IPPROTOUDP}elseif(fltdevobj==gipfltobj){result=gipoldobjipproto=IPPROTOIP}else{KdPrint(("tdifwgetoriginaldevobj:UnknownDeviceObjectxx!n",fltdevobj))ipproto=IPPROTOIPwhatelseresult=}if(result!=proto!=)*proto=ipprotoreturnresult}getoriginaldevobj之所以能这样做是因为它已经事先把各个协议的设备以及我们生成的新设备的指针保存在全局变量里了也就是上面的gtcpfltobjgtcpoldobjgudpfltobjgudpoldobj这些变量。简单比较一下就行。()基础过滤框架(第/页)WindowsTDI过滤驱动开发那么我们可以写一个基础的过滤框架了:#include<ntddkh>#include<tdikrnlh>保存设备指针的全局变量PDEVICEOBJECTgtcpfltobj=,DeviceTcpgudpfltobj=,DeviceUdpgipfltobj=,DeviceRawIpgtcpoldobj=,DeviceTcpgudpoldobj=,DeviceUdpgipoldobj=DeviceRawIp卸载函数VOIDOnUnload(INPDRIVEROBJECTDriverObject){我对已生成和绑定的设备进行解绑和删除if(gtcpoldobj!=)IoDetachDevice(gtcpoldobj)if(gtcpfltobj!!=)IoDeleteDevice(gtcpfltobj)if(gudpoldobj!=)IoDetachDevice(gudpoldobj)if(gudpfltobj!!=)IoDeleteDevice(gudpfltobj)if(gipoldobj!=)IoDetachDevice(gipoldobj)if(gipfltobj!!=)IoDeleteDevice(gipfltobj)}驱动入口NTSTATUSDriverEntry(INPDRIVEROBJECTtheDriverObject,INPUNICODESTRINGtheRegistryPath){NTSTATUSstatus=STATUSSUCCESSinti设置分发函数for(i=i<IRPMJMAXIMUMFUNCTIONi)theDriverObject>MajorFunctioni=DeviceDispatchtheDriverObject>DriverUnload=OnUnload绑定几个TDI设备status=cnadevice(theDriverObject,gtcpfltobj,gtcpoldobj,L"DeviceTcp")(第/页)WindowsTDI过滤驱动开发if(status!=STATUSSUCCESS){KdPrint(("tdifwDriverEntry:cnadevice:xxn",status))gotodone}status=cnadevice(theDriverObject,gudpfltobj,gudpoldobj,L"DeviceUdp")if(status!=STATUSSUCCESS){KdPrint(("tdifwDriverEntry:cnadevice:xxn",status))gotodone}status=cnadevice(theDriverObject,gipfltobj,gipoldobj,L"DeviceRawIp")if(status!=STATUSSUCCESS){KdPrint(("tdifwDriverEntry:cnadevice:xxn",status))gotodone}done:如果失败了if(status!=STATUSSUCCESS){OnUnload(theDriverObject)}returnstatus}里面调用的所有函数上面都提及了现在你可以编译这个驱动并动态加载了这就是我们第一个TDI过滤驱动的框架。()主要过滤的请求类型我们已经完成了进行过滤的框架下面的任务是了解该过滤什么假设作为一个防火墙或者一个安全监控软件我希望监控计算机内的软件对网络进行的访问比如说连接请求连接ip地址端口等等。现在必须进一步了解TDI接口的请求方式。所有的请求都被封装为IRP并在DeviceDispatch(见前面我们所写的请求处理函数DeviceDispatch)中处理。IRP以主功能号和辅功能号决定它的类别在DeviceDispatch中获得主功能号码的代码入下:NTSTATUSDeviceDispatch(INPDEVICEOBJECTDeviceObject,INPIRPirp){NTSTATUSstatusPIOSTACKLOCATIONirps(第/页)WindowsTDI过滤驱动开发获取当前irp栈空间irps=IoGetCurrentIrpStackLocation(irp)switch(irps>MajorFunction){生成请求caseIRPMJCREATE:break设备控制请求caseIRPMJDEVICECONTROL:break内部设备控制请求caseIRPMJINTERNALDEVICECONTROL:breakcaseIRPMJCLOSE:breakcaseIRPMJCLEANUP:breakdefault:}returnstatus}次功能号则在irps>MiniorFunction中。根据不同的主功能号有不通的次功能号不能给出通用的例子应该逐一分析。同时有时一些主功能下并没有次功能的区分这样次功能号并不总是有意义。IRPMJDEVICECONTROL和IRPMJINTERNALDEVICECONTROL的情况次功能号用得比较多。对于老手而言只需要了解IRPMJCREATE,IRPMJDEVICECONTROL和IRPMJINTERNALDEVICECONTROL是TDI接口中最重要的三种请求就可以了新手可能需(第/页)WindowsTDI过滤驱动开发要花费一些功夫了解IRP栈空间这样的概念。但是我认为不了解也可以继续阅读你只需要知道这是获取请求类型的一个必须过程罢了。前面的DeviceDispatch函数中把请求发送给真实设备的时候也和栈空间有关有兴趣的读者可以阅读DDK中关于"请求"IRP的相关介绍。TDI接口的调用总是遵循着打开>设备控制>关闭的流程我们将截取这些IRP中的信息,来进行我们的过滤()CREATE的过滤对于CREATE的irp的过滤一般有以下几个步骤:得到当前进程:一些防火墙喜欢显示出使用一个连接的进程我们要得到生成连接的进程这就是机会非常简单:ULONGpid=(ULONG)PsGetCurrentProcessId()在处理Create的irp的进程中调用即可。获取EA数据打开DDK查看打开设备的核心APIZwCreateFile的文档:NTSTATUSZwCreateFile(OUTPHANDLEFileHandle,INACCESSMASKDesiredAccess,INPOBJECTATTRIBUTESObjectAttributes,OUTPIOSTATUSBLOCKIoStatusBlock,INPLARGEINTEGERAllocationSizeOPTIONAL,INULONGFileAttributes,INULONGShareAccess,INULONGCreateDisposition,INULONGCreateOptions,INPVOIDEaBufferOPTIONAL,INULONGEaLength)这里的EaBuffer我们在平时的开发中极少使用但是TDI操作却几乎全靠Ea数据来传递信息微软这样做的理由让人无法得知现在我们获得Ea数据:FILEFULLEAINFORMATION*ea=(FILEFULLEAINFORMATION*)irp>AssociatedIrpSystemBuffer(第/页)WindowsTDI过滤驱动开发具体的tdi操作请求指令的名称就保存在ea>EaName中。而名字的内容在CREATE中有以下两个预先定义的宏:TdiTransportAddress:表明目前Create的是一个传输层地址。TdiConnectionContext:表明目前生成一个连接终端。而且两个字符串的长度应该分别为:TDITRANSPORTADDRESSLENGTH和TDICONNECTIONCONTEXTLENGTH。下面是tdifw中进行过滤的方法假设我们在DeviceDispatch中调用tdicreate来进行CreateIRP的过滤。inttdicreate(PIRPirp,PIOSTACKLOCATIONirps,structcompletion*completion){NTSTATUSstatusFILEFULLEAINFORMATION*ea=(FILEFULLEAINFORMATION*)irp>AssociatedIrpSystemBufferULONGpid=(ULONG)PsGetCurrentProcessId()if(ea!=){if(ea>EaNameLength==TDITRANSPORTADDRESSLENGTHmemcmp(ea>EaName,TdiTransportAddress,TDITRANSPORTADDRESSLENGTH)==){在这里捕获传输层地址生成}}elseif(ea>EaNameLength==TDICONNECTIONCONTEXTLENGTHmemcmp(ea>EaName,TdiConnectionContext,TDICONNECTIONCONTEXTLENGTH)==){在这里捕获连接终端的生成}}之后分别对这两种情况进行进一步的信息解析。(第/页)WindowsTDI过滤驱动开发()准备解析ip地址与端口我们的安全软件要监控的是IP地址与端口。这可以通过给CREATEIRP生成传输地址的之后向该FILEOBJECT发送一个QUERYIRP来获得。这件事的困难在于只有当这个IRP被发到下层完成之后才能发送Query请求来询问IP地址与端口tdifw的做法是将一些信息保存到指针completion所指的区域内。不要为不能理解的概念而烦恼我们看下面的代码:生成的处理inttdicreate(PIRPirp,PIOSTACKLOCATIONirps,structcompletion*completion){if(ea!=){if(ea>EaNameLength==TDITRANSPORTADDRESSLENGTHmemcmp(ea>EaName,TdiTransportAddress,TDITRANSPORTADDRESSLENGTH)==){传输层地址生成的处理我们必须询问被打开的FILEOBJECT来获得IP地址和端口询问需要一个IRP。我们调用TdiBuildInternalDeviceControlIrp来分配一个空的IRP。因为完成函数往往不在PASSIVELEVEL所以我们在这里生成它。queryirp=TdiBuildInternalDeviceControlIrp(TDIQUERYINFORMATION,devobj,irps>FileObject,,)if(queryirp==){KdPrint(("tdifwtdicreate:TdiBuildInternalDeviceControlIrpn"))returnFILTERDENY}指定一个完成函数这样如果CREATEIRP一完成就会调用这个函数我们可以在其中询问IP地址和端口。completion>routine=tdicreateaddrobjcomplete同时把我们已分配的IRP记录下来在之后使用completion>context=queryirp}}这里涉及到一个中断级别的问题已知的是IRP的完成函数往往在DISPATCHLEVEL被调用这个中断级上不能调用TdiBuildInternalDeviceControlIrp。而分发函数一般都在PASSIVELEVEL调用TdiBuildInternalDeviceControlIrp是合适的我认为这些概念不理解也并不影响阅读。回到前面的DeviceDispatch函数里面是这样调用tdicreate的:(第/页)WindowsTDI过滤驱动开发分发函数NTSTATUSDeviceDispatch(INPDEVICEOBJECTDeviceObject,INPIRPirp){switch(irps>MajorFunction){caseIRPMJCREATE:*createfileobject*result=tdicreate(irp,irps,completion)status=tdidispatchcomplete(DeviceObject,irp,result,completionroutine,completioncontext)break}}显然要查询地地址时IRP将在tdidispatchcomplete中被完成。完成后tdicreateaddrobjcomplete被调用来询问IP地址我们在下一节里继续。()获取生成的IP地址和端口上回说到我们对一个CREATIRP进行过滤。当其中含有的TDI功能命令为TdiTransportAddress我们得知一个地址正在生成(实际上是一个连接的本地地址)。只有等这个IRP下发完成之后我们才可以通过对这个被打开的FILEOBJECT发送QUERYIRP得到生成的地址。那么我们将用到IRP的完成函数。完成函数将在这个IRP被下层完成后调用。你可以如下的设置一个完成函数:IoSetCompletionRoutine(irp,mycomplete,context,TRUE,TRUE,TRUE)这里的mycomplete就是我们的完成函数。考虑到上回我们将在地址生成之后进行查询那么你可以把这个函数指定为tdicreateaddrobjcomplete。context是一个上下文。当mycomplete被调用的时候你需要很多参数比如以前你分配的irp指针你需要保存的其他信息都可以通过(第/页)WindowsTDI过滤驱动开发这个指针传入。我们曾经用TdiBuildInternalDeviceControlIrp分配了一个空的irp。那么我们现在应该设置它。指定我们的查询动作。TdiBuildQueryInformation(queryirp,devobj,irps>FileObject,tdicreateaddrobjcomplete,context,TDIQUERYADDRESSINFO,mdl)这里queryirp是上面我们分配过的irp指针。devobj这里要指定为真实的设备irps>FileObject是打开的FILEOBJECT这个irps从原来的CREATEIRP中取得。PIOSTACKLOCATIONirps=IoGetCurrentIrpStackLocation(Irp)TDIQUERYADDRESSINFO是一个查询码。表示我们要查询的是地址信息。tdicreateaddrobjcomplete是我们这个查询irp完成之后所调用的完成函数。而mdl是一块内存区。mdl的分配方法为:TDIADDRESSINFO*tai=(TDIADDRESSINFO*)mallocnp(TDIADDRESSINFOMAX)PMDLmdl=IoAllocateMdl(tai,TDIADDRESSINFOMAX,FALSE,FALSE,)MmBuildMdlForNonPagedPool(mdl)mallocnp是tdifw使用的内存分配函数。如果你不用tdifw,可以调用ExAllocatePool。TDIADDRESSINFO是一个传输层地址信息结构。里面可以含有任何传输层协议的地址。然后发出这个irp即可:status=IoCallDriver(devobj,queryirp)那么最后对IP地址和端口的解析发生在tdicreateaddrobjcomplete中:tdicreateaddrobjcomplete(INPDEVICEOBJECTDeviceObject,INPIRPIrp,INPVOIDContext){if(Irp>MdlAddress){得到Mdl所指的地址TDIADDRESSINFO*tai=(TDIADDRESSINFO*)MmGetSystemAddressForMdl(Irp>MdlAddress)得到一个地址结构TAADDRESS*addr=tai>AddressAddress打印取得的信息KdPrint(("tdifwtdicreateaddrobjcomplete:address:x:un",ntohl(((TDIADDRESSIP*)(addr>Address))>inaddr),ntohs(((TDIADDRESSIP*)(addr>Address))>sinport)))}(第/页)WindowsTDI过滤驱动开发}当然还有个问题就是你得自己设法把你取得的ip地址和端口保存起来。()连接终端的生成与相关信息的保存前面我们费很多工夫获得了当一个地址生成时取得其IP地址和端口的代码。回忆一下前面对CREATEIRP的过滤有两种生成请求:TdiTransportAddress:表明目前Create的是一个传输层地址TdiConnectionContext:表明目前生成一个连接终端我们已经处理了第一种。处理过程颇为费事那么当连接终端生成时我们需要做什么呢?这里要考虑下TDI的建立连接的方式。其过程总是:先生成一个传输层地址。然后生成

热点搜索换一换

用户评论(0)

0/200

精彩专题

上传我的资料

每篇奖励 +1积分

资料评分:

/29
0下载券 下载 加入VIP, 送下载券

意见
反馈

立即扫码关注

爱问共享资料微信公众号

返回
顶部

举报
资料