驱动开发基础知识_第1页
驱动开发基础知识_第2页
驱动开发基础知识_第3页
驱动开发基础知识_第4页
驱动开发基础知识_第5页
已阅读5页,还剩18页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第六章:内核编程介绍一、内核开发入门一)内核编程的环境准备从现在开始如何编写内核程序和一些注意事项,内核程序运行在R0层,拥有最高权限,在这里我假设你有足够的VC开发上层应用程序的经验。开发内核程序需要准备的开发环境:VisualStudio6/2005/2010均可,微软的驱动程序开发包DDK(DriverDevelopmentKit),随着Vista版本的发布这个开发包升级为WDK(WindowsDriverKit)开发内核程序需要准备的调试环境:虚拟机系统,Windbg调试程序,Dbgview.exe查看日志程序。二)编写第一个驱动程序与普通的应用程序相比,在一个内核程序工程中,需要增加如下两个文件:Sources文件内容如下:Sources文件内容如下:TARGETNAME=collectorTARGETPATH三TARGETTYPE=DRIVERDRIVERTYPE=FS〃编译出来的目标文件文件名〃目标文件路径默认路径TARGETLIBS=$(DDK_LIB_PATH)\wdmsec.lib\..\lib\drvlib.lib〃静态库INCLUDES=$(DDK_INC_PATH);\..\include〃头文件SOURCES=filespy.c\〃源文件drv.rc其他更详细的信息,请到网上查阅相关资料。Makefile文件内容如下:#DONOTEDITTHISFILE!!!Edit.'sources,ifyouwanttoaddanewsourcefiletothiscomponent.ThisfilemerelyindirectstotherealmakefilethatissharedbyallthedrivercomponentsoftheWindowsNTDDK#[INCLUDE$(NTMAKEENV)\makefile.def没有仔细研究过,写成这样就可以编译通过。更多细节请查阅相关资料。看看驱动的DriverEntry函数:NTSTATUSDriverEntry(INPDRIVER_OBJECTDriverObject,INPUNICODE_STRINGRegistryPath){DbgPrint("OK,thisisDriverEntry!");DbgPrint("DriverEntryisover\n");returnSTATUS_SUCCESS;J 当然这个程序什么也没有做,只是进去打了两句日志就退出了。通过Dbgview可以查看到本程序打印出来的日志。二、驱动开发基础(一)浅谈驱动对象、设备对象与请求首先,谈谈驱动对象(DRIVER_OBJECT),可以说驱动对象代表的是一个驱动程序(或者叫内核模块)。在写内核程序时,必须要填写这样一种结构,来告诉Windows程序提供的功能。内核程序并不生成进程,它们有系统的System进程加载,可以存在于任何的进程。设备对象(DEVICE_OBJECT)可以是一个具体的物理设备,如:键盘、硬盘等;也可以是一个虚拟的“设备”,如:用于进程间通信的管道。设备对象由驱动对象创建一个驱动对象可以创建很多个设备对象。这些设备对象储存在一个设备栈中,这些设备对象是用链表连接在一起的,当新的设备对象产生时应该是用的尾插入(和人理解)。对于请求,我们可以理解为WindowsSDK程序设计里的消息。一般都是以IRP方式传递的。而设备对象是唯一可以接受请求的实体。然而一个驱动对象中可能会有很多个设备对象,那么是由哪一个设备对象来处理呢?我的理解是:就像MFC中的消息传递机制一样,会有一个消息的接收顺序;IRP请求也是这样,它先发送到设备栈中最上面的一个设备(最新加入),没有被处理就继续向下发送,如果到最后都还是没有被处理,我认为会有一个默认的处理。(二)IoCallDriver函数与PoCallDriver函数首先来看这两个函数的原型:NTSTATUSIoCallDriver(INPDEVICEOBJECTDeviceObject,INOUTPIRPIrp);NTSTATUSPoCallDriver(INPDEVICE_OBJECTDeviceObject,INOUTPIRPIrp); 除了函数名不同之外,其他都一样。参数都是两个,一个是设备对象的指针,另一个是IRP请求对象的指针。返回值也是一样。那么区别到底是什么呢?我们来看WDKDocumentation上的解释:TheIoCallDriverroutinesendsanIRPtothedriverassociatedwithaspecifieddeviceobject.ThePoCallDriverroutinepassesapowerIRPtothenext-lowerdriverinthedevicestack.(WindowsServer2003,WindowsXP,andWindows2000only.)从上面的这两句话中可以看出:IoCallDriver这个函数向DeviceObject设备对象的驱动对象发送一个IRP请求;而PoCallDriver函数向设备栈中的下层设备传递一个主功能号为IRP_MJ_POWER的请求,且限于特定的OS。而且,调用IoCallDriver之前,主调驱动程序必须要为目标驱动程序建立IRP里的I/Ostacklocation;同时,调用时,IoCallDriver函数还会帮助驱动程序将输入参数的DeviceObject值赋给IO_STACK_LOCATION结构里的DeviceObject成员。(三)irp请求处理及完成机制近来学习Windows内核方面的东西,觉得对I/O处理过程没有一个总体的概念。于是,就花了很长的时间搜集了很多这方面的知识总结了一下。在Windows内核中的请求基本上是通过I/ORequestPacket完成的。前面说过,设备对象是唯一可以接受请求的实体。下面,我就来详细地说下IRP请求是怎么样一步一步完成的。首先,我们就需要知道IRP是怎么产生。IRP是由I/O管理器发出的,I/O管理器是用户态与内核态之间的桥梁,当用户态进程发出I/O请求时,I/O管理器就捕获这些请求,将其转换为IRP请求,发送给驱动程序。I/O管理器无疑是非常重要的,具有核心地位。它负责所有I/O请求的调度和管理工作,根据请求的不同内容,选择相应的驱动程序对象,设备对象,并生成、发送、释放各种不同的IRP。整个I/O处理流程是在它的指挥下完成的。一个IRP是从非分页内存中分配的可变大小的结构,它包括两部分:IRP首部和辅助请求参数数组,如图1所示。这两部分都是由I/O管理器建立的。图1IRP简单结构图IRP首部中包含了指向IRP输入输出缓冲区指针、当前拥有IRP的驱动指针等。紧接着首部的是一个IO_STACK_LOCATION结构的数组。它的大小由设备栈中的设备数确定(设备栈的概念会在下文中阐述)。IO_STACK_LOCATION结构中保存了一个I/O请求的参数及代码、请求当前对应的设备指针、完成函数指针(IoCompletion)等。那么,由I/O管理器产生的IRP请求发送到哪去了呢?这里我们就要来说说设备栈的概念了,操作系统用设备对象(deviceobject)表示物理设备,每一个物理设备都有一个或多个设备对象与之相关联,设备对象提供了在设备上的所有操作。也有一些设备对象并不表示物理设备。一个唯软件驱动程序(software-onlydriver,处理I/O请求,但是不把这些请求传递给硬件)也必须创建表示它的操作的设备对象。设备常常由多个设备对象所表示,每一个设备对象在驱动程序栈(driverstack)中对应一个驱动程序来管理设备的I/O请求。一个设备的所有设备对象被组织成一个设备栈(devicestack)。而且,IO_STACK_LOCATION数组中的每个元素和设备栈中的每个设备是一一对应的,一般情况下,只允许层次结构中的每个设备对象访问它自己对应的IO_STACK_LOCATION。无论何时,一个请求操作都在一个设备上被完成,I/O管理器把IRP请求传递给设备栈中顶部设备的驱动程序(IRP是传递给设备对象的,通过设备对象的DriverObject成员找到驱动程序)。驱动程序访问它对应的设备对象在IRP中IO_STACK_LOCATION数组中的元素检查参数,以决定要进行什么操作(通过检查结构中的MajorFunction字段,确定执行什么操作及如何解释Parameters共用体字段的内容)。驱动程序可以根据IO_STACK_LOCATION结构中的MajorFunction字段进行处理。每一个驱动或者处理IRP,或者把它传递给设备栈中下一个设备对象的驱动程序。传递IRP请求到底层设备的驱动程序需要经过下面几个步骤:1.为下一个IO_STACK_LOCATION结构设置参数。可以有以下两种方式:•调用IoGetNextlrpStackLocation函数获得下个结构的指针,再对参数进行赋值;•调用IoCopyCurrentIrpStackLocationToNext函数(如果第2步中驱动设置了IoCompletion函数),或者调用IoSkipCurrentIrpStackLocation函数(如果第2步中驱动没有设置IoCompletion函数)把当前的参数传递给下一个。如果需要的话,调用IoSetCompletionRoutine函数设置IoCompletion函数进行后续处理。调用IoCallDriver函数将IRP请求传递给下一层驱动。这个函数会自动调整IRP栈指针,并且执行下一层驱动的派遣函数。

当驱动程序把IRP请求传递给下一层驱动之后,它就不再拥有对该请求的访问权,强行访问会导致系统崩溃。如果驱动程序在传递完之后还想再访问该请求,就必须要设置IoCompletion函数。IRP请求可以再其他驱动程序或者其他线程中完成或取消。当某一驱动程序调用IoCompleteRequest函数时,I/O操作就完成了。这个函数使得IRP的堆栈指针向上移动一个位置,如图2所示:IRP

stad<

poiriterIRPHeaderCurrentI/OstacklocationIRP

stad<

poiriterIRPHeaderCurrentI/OstacklocationloStatus.Status1oStatus.InformationParameterlforAParametersforACallbad<forinitiatorParameterlforBp3r3iTiHtHr2forB/oGown'efiW?routineforAParameterlforCParametersfor-C-foCacaplettonroutineforBIF:P

ccimpletion图2IRP完成时栈指针的移动图2所示的当C驱动程序调用完IoCompleteRequest函数后I/O栈的情况。左边的实线箭头表明栈指针现在指向驱动B的参数和回调函数;虚线箭头是之前的情况。右边的空心箭头指明了IoCompletion函数被调用的顺序。如果驱动程序把IRP请求传递给设备栈中的下层设备之前设置了IoCompletion函数,当I/O栈指针再次指回到该驱动程序时,I/O管理器就将调用该IoCompletion函数。IoCompletion函数的返回值有两种:(1)STATUS_CONTINUE_COMPLETION:告诉I/O管理器继续执行上层驱动程序的IoCompletion函数。(2)STATUS_MORE_PROCESSING_REQUIRED:告诉I/O管理器停止执行上层驱动程序,并将栈指针停在当前位置。在当前驱动程序调用IoCompleteRequest函数后再继续执行上层驱动的IoCompletion函数。当所有驱动都完成了它们相应的子请求时,I/O请求就结束了。I/O管理器从Irp->IoStatus.Status成员更新状态信息,从Irp->IoStatus.Information成员更新传送字节数。四)编写程序手动加载驱动程序#includevwindows.h>#include<winsvc.h>#include<conio.h>#include<stdio.h>#defineDRIVER_NAME"HelloDriver"#defineDRIVER_PATH"..\\MyDriver\\HelloDriver.sys"〃装载NT驱动程序BOOLLoadNTDriver(char*lpszDriverName,char*lpszDriverPath){charszDriverImagePath[256];〃得到完整的驱动路径GetFullPathName(lpszDriverPath,256,szDriverImagePath,NULL);BOOLbRet=FALSE;SC_HANDLEhServiceMgr=NULL;//SCM管理器的句柄SC_HANDLEhServiceDDK=NULL;//NT驱动程序的服务句柄〃打开服务控制管理器hServiceMgr=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);if(hServiceMgr==NULL){//OpenSCManager失败printf("OpenSCManager()Faild%d!\n",GetLastError());bRet=FALSE;gotoBeforeLeave;}else{////OpenSCManager成功printf("OpenSCManager。ok!\n");}〃创建驱动所对应的服务hServiceDDK=CreateService(hServiceMgr,lpszDriverName,//驱动程序的在注册表中的名字lpszDriverName,//注册表驱动程序的DisplayName值SERVICE_ALL_ACCESS,//加载驱动程序的访问权限SERVICE_KERNEL_DRIVER,〃表示加载的服务是驱动程序SERVICE_DEMAND_START,//注册表驱动程序的Start值SERVICE_ERROR_IGNORE,//注册表驱动程序的ErrorControl值szDriverImagePath,//注册表驱动程序的ImagePath值NULL,NULL,NULL,NULL,NULL);DWORDdwRtn;〃判断服务是否失败if(hServiceDDK==NULL){dwRtn=GetLastError();if(dwRtn!=ERROR_IO_PENDING&&dwRtn!=ERROR_SERVICE_EXISTS){〃由于其他原因创建服务失败printf("CrateService()Faild%d!\n",dwRtn);bRet=FALSE;gotoBeforeLeave;

else{〃服务创建失败,是由于服务已经创立过printf("CrateService()FaildServiceisERROR_ERROR_SERVICE_EXISTS!\n");}//驱动程序已经加载,只需要打开hServiceDDK=OpenService(hServiceMgr,SERVICE_ALL_ACCESS);if(hServiceDDK==NULL){〃如果打开服务也失败,则意味错误dwRtn=GetLastError();printf("OpenService()Faild%d!\n",dwRtn);bRet=FALSE;gotoBeforeLeave;}else{printf("OpenService()ok!\n");}}else{printf("CrateService()ok!\n");}〃开启此项服务bRet=StartService(hServiceDDK,NULL,NULL);if(!bRet){DWORDdwRtn=GetLastError();if(dwRtn!=ERROR_IO_PENDING&&ERROR_SERVICE_ALREADY_RUNNING){printf("StartService()Faild%d!\n",dwRtn);bRet=FALSE;gotoBeforeLeave;}else{if(dwRtn==ERROR_IO_PENDING){_PENDINGorlpszDriverName,dwRtn!=〃设备被挂住_PENDINGorlpszDriverName,dwRtn!=printf("StartService()FaildERRORIOPENDING!\n");bRet=FALSE;gotoBeforeLeave;}else{〃服务已经开启printf("StartService()FaildERROR_SERVICE_ALREADY_RUNNING!\n");bRet=TRUE;gotoBeforeLeave;}}}bRet=TRUE;〃离开前关闭句柄BeforeLeave:if(hServiceDDK){CloseServiceHandle(hServiceDDK);}if(hServiceMgr){CloseServiceHandle(hServiceMgr);}returnbRet;}〃卸载驱动程序BOOLUnloadNTDriver(char*szSvrName){BOOLbRet=FALSE;SC_HANDLEhServiceMgr=NULL;//SCM管理器的句柄SC_HANDLEhServiceDDK=NULL;//NT驱动程序的服务句柄SERVICE_STATUSSvrSta;〃打开SCM管理器hServiceMgr=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);if(hServiceMgr==NULL){〃带开SCM管理器失败printf("OpenSCManager()Faild%d!\n",GetLastError());bRet=FALSE;gotoBeforeLeave;}else{〃带开SCM管理器失败成功printf("OpenSCManager()ok!\n");}〃打开驱动所对应的服务hServiceDDK=OpenService(hServiceMgr,szSvrName,SERVICE_ALL_ACCESS);if(hServiceDDK==NULL){〃打开驱动所对应的服务失败printf("OpenService()Faild%d!\n",GetLastError());bRet=FALSE;gotoBeforeLeave;}else{printf("OpenService()ok!\n");}〃停止驱动程序,如果停止失败,只有重新启动才能,再动态加载。if(!ControlService(hServiceDDK,SERVICE_CONTROL_STOP,&SvrSta)){printf("ControlService()Faild%d!\n",GetLastError());}else{〃打开驱动所对应的失败printf("ControlService()ok!\n");}〃动态卸载驱动程序。if(!DeleteService(hServiceDDK)){〃卸载失败printf("DeleteSrevice()Faild%d!\n",GetLastError());}else{〃卸载成功printf("DelServer:eleteSrevice()ok!\n");}bRet=TRUE;BeforeLeave:〃离开前关闭打开的句柄if(hServiceDDK){CloseServiceHandle(hServiceDDK);}if(hServiceMgr){CloseServiceHandle(hServiceMgr);}returnbRet;}voidTestDriver(){〃测试驱动程序HANDLEhDevice=CreateFile("\\\\.\\HelloDDK",GENERIC_WRITEIGENERIC_READ,0,NULL,OPEN_EXISTING0,NULL);if(hDevice!=INVALID_HANDLE_VALUE){printf("CreateDeviceok!\n");}else{printf("CreateDevicefaild%d!\n",GetLastError());}CloseHandle(hDevice);}intmain(intargc,char*argv[]){〃加载驱动BOOLbRet=LoadNTDriver(DRIVER_NAME,DRIVER_PATH);if(!bRet){printf("LoadNTDrivererror\n");return0;}〃加载成功printf("pressanytocreatedevice!\n");getch();TestDriver();〃这时候你可以通过注册表,或其他查看符号连接的软件验证。printf("pressanytounloadthedriver!\n");getch();〃卸载驱动UnloadNTDriver(DRIVERNAME);if(!bRet){printf("UnloadNTDrivererror\n");return0;}return0;}(五)创建IRP的四种不同方式在驱动程序中,经常会调用其他的驱动程序;其中,手动构造IRP,然后将IRP传递到相应驱动程序的派遣函数中是一种比较简单的方法,下面就来介绍下手动创建IRP的几种不同的方法及其特点。创建IRP总共有4种方法。分别通过调用:IoBuildSynchronousFsdRequest、IoBuildAsynchronousFsdRequest、IoBuildDeviceIoControl和IoAllocateIrp这4个内核函数来完成。这其中,IoAllocateIrp是比较底层的内核函数,其余的三个内核函数是属于靠近上层的内核函数,而且这三个函数都是通过调用IoAllocateIrp实现的。这几个函数都是文档化的函数,原型都可以在DDKDocumentation中查到,这里就不多说了,下面主要来说说它们的不同点:1.可创建的IRP类型这四个函数可以创建的IRP的类型是不同的。IoBuildSynchronousFsdRequest用于创建同步的IRP请求,但是只可以创建以下类型的IRP:IRP_MJ_PNP,IRP_MJ_READ,IRP_MJ_WRITE,IRP_MJ_FLUSH_BUFFERS和IRP_MJ_SHUTDOWN;IoBuildAsynchronousFsdRequest可创建的IRP类型和IoBuildSynchronousFsdRequest一样(从名字就可以看出来),只是它是用来创建异步的IRP请求。IoBuildDeviceIoControl可以创建的IRP类型为:IRP_MJ_DEVICE_CONTROL和IRP_MJ_INTERNAL_DEVICE_CONTROL。而且IoBuildDeviceIoControl只能创建同步的IRP。在这三个函数中,都有一个ULONG的输入参数指定创建的IRP类型。IoAllocateIrp函数的使用比较灵活,他可以创建任意类型的IRP,但不是由参数指定,而是创建后自行填写,要求用户对IRP的结构有比较熟悉的理解。2•创建后IRP对象的删除IoBuildSynchronousFsdRequest、IoBuildAsynchronousFsdRequest和IoBuildDeviceIoControl内核函数在创建完IRP后,不需要程序员负责删除IRP,操作系统会自动删除。而用IoAllocateIrp内核函数创建IRP时,需要程序员自己调用IoFreeIrp内核函数删除IRP对象。3.关联的事件IoBuildSynchronousFsdRequest和IoBuildDeviceIoControl在创建IRP时,需要为它们准备好一个事件,这个事件会和IRP请求相关联,当IRP请求被结束时该事件触发。程序中要用KeWaitForSingleObject函数等待。IoBuildAsynchronousFsdRequest函数创建IRP时则不需要准备事件,不过可以通过IRP的UserEvent子域来通知IRP请求的结束。当执行IoCompleteRequest内核函数时,操作系统会检查IRP的UserEvent子域是否为空。如果该子域为空,则它代表一个事件指针,这时IoCompleteRequest会设置这个事件。六)驱动和应用层的三种通信方式驱动程序和客户应用程序经常需要进行数据交换,但我们知道驱动程序和客户应用程序可能不在同一个地址空间,因此操作系统必须解决两者之间的数据交换。驱动层和应用层通信,主要是靠DeviceIoControl函数,下面是该函数的原型:BOOLDeviceIoControl(HANDLEhDevice,//设备句柄DWORDdwIoControlCode,//IOCTL请求操作代码LPVOIDlpInBuffer,//输入缓冲区地址DWORDnInBufferSize,//输入缓冲区大小LPVOIDlpOutBuffer,//输出缓冲区地址DWORDnOutBufferSize,//输出缓冲区大小LPDWORDlpBytesReturned,//存放返回字节数的指针LPOVERLAPPEDlpOverlapped//用于同步操作的Overlapped结构体指针);dwIoControlCode要进行操作的控制码。驱动程序可以通过CTL_CODE宏来组合定义一个控制码,并在IRP_MJ_DEVICE_CONTROL的实现中进行控制码的操作。在驱动层,irpStack->Parameters.DeviceIoControl.IoControlCode表示了这个控制码。//////////////////////////////////////////////////////////////////////////////////////////////////////////////////IoBuildDeviceIoControl可以创建的IRP类型为IRP_MJ_DEVICE_CONTROL和IRP_MJ_INTERNAL_DEVICE_CONTROL。而且IoBuildDeviceIoControl只能创建同步的IRP//////////////////////////////////////////////////////////////////////////////////////////////////////////////////IOCTL请求有四种缓冲策略,下面一一介绍。1、 输入输出缓冲I/O(METHOD_BUFFERED)2、 直接输入缓冲输出I/O(METHOD_IN_DIRECT)3、 缓冲输入直接输出I/O(METHOD_OUT_DIRECT)4、 上面三种方法都不是(METHOD_NEITHER)为了对这些类型更详细的描述,请看msdn上的解释,我抄录如下:"缓冲”方法(METHOD_BUFFERED)备注:在下面的讨论中,"输入"表示数据从用户模式的应用程序到驱动程序,"输出"表示数据从驱动程序到应用程序。对于读取请求,I/O管理器分配一个与用户模式的缓冲区大小相同的系统缓冲区。IRP中的SystemBuffer字段包含系统地址。UserBuffer字段包含初始的用户缓冲区地址。当完成请求时,I/O管理器将驱动程序已经提供的数据从系统缓冲区复制到用户缓冲区。对于写入请求,会分配一个系统缓冲区并将SystemBuffer设置为地址。用户缓冲区的内容会被复制到系统缓冲区,但是不设置UserBuffer。对于IOCTL请求,会分配一个容量大小足以包含输入缓冲区或输出缓冲区的系统缓冲区,并将SystemBuffer设置为分配的缓冲区地址。输入缓冲区中的数据复制到系统缓冲区0UserBuffer字段设置为用户模式输出缓冲区地址。内核模式驱动程序应当只使用系统缓冲区,且不应使用UserBuffer中存储的地址。对于IOCTL,驱动程序应当从系统缓冲区获取输入并将输出写入到系统缓冲区。当完成请求时,I/O系统将输出数据从系统缓冲区复制到用户缓冲区。"直接”方法(METHOD_IN/OUT_DIRECT)对于读取和写入请求,用户模式缓冲区会被锁定,并且会创建一个内存描述符列表(MDL)。MDL地址会存储在IRP的MdlAddress字段中。SystemBuffer和UserBuffer均没有任何含义。但是,驱动程序不应当更改这些字段的值。对于IOCTL请求,如果在METHOD_IN_DIRECT和METHOD_OUT_DIRECT中同时有一个输出缓冲区,则分配一个系统缓冲区(SystemBuffer又有了地址)并将输入数据复制到其中。如果有一个输出缓冲区,且它被锁定,则会创建MDL并设置MdlAddress。UserBuffer字段没有任何含义。"两者都不"方法(METHOD_NEITHER)对于读取和写入请求,UserBuffer字段被设置为指向初始的用户缓冲区。不执行任何其他操作。SystemAddress和MdlAddress没有任何含义。对于IOCTL请求,I/O管理器将UserBuffer设置为初始的用户输出缓冲区,而且,它将当前I/O栈位置的Parameters.DeviceIoControl.Type3InputBuffer设置为用户输入缓冲区。利用该I/O方法,由驱动程序来确定如何处理缓冲区:分配系统缓冲区或创建MDL。通常,驱动程序在访问用户数据时不应当将UserBuffer字段用作地址,即使当用户缓冲区被锁定时也是如此。这是由于在调用驱动程序时,在系统中可能看不到调用用户的地址空间。(对于该规则的一个例外是,在最高层驱动程序将IRP向下传递到较低层的驱动程序之前,它可能需要使用UserBuffer来复制数据。)如果使用"直接"或"两者都不"方法,在创建MDL之后,驱动程序可以使用MmGetSystemAddressForMdl函数来获取有效的系统地址以访问用户缓冲区。在驱动层,依传输类型的不同,输入缓冲区的位置亦不同,见下表。传输类型 位置METHOD_IN_DIRECT irp->AssociatedIrp.SystemBufferMETHOD_OUT_DIRECT irp->AssociatedIrp.SystemBufferMETHOD_BUFFERED irp->AssociatedIrp.SystemBufferMETHOD_NEITHERirpStack->Parameters.DeviceIoControl.Type3InputBuffer在驱动层,依传输类型的不同,输出缓冲区的位置亦不同,见下表。传输类型 位置METHOD_IN_DIRECT irp->MdlAddressMETHOD_OUT_DIRECT irp->MdlAddressMETHOD_BUFFERED irp->AssociatedIrp.SystemBufferMETHOD_NEITHERirp->UserBuffer所以只要确定了传输方式后,就可以根据各自的位置来读取和写入数据,从而实现应用层和驱动的通信。下面看驱动层对ioctl控制码的处理代码:代码://METHOD_OUT_DIREC方式NTSTATUSCOMM_DirectOutIo(PIRPIrp,PIO_STACK_LOCATIONploStacklrp,UINT*sizeofWrite){NTSTATUSstatus=STATUS_UNSUCCESSFUL;PVOIDpInputBuffer,pOutputBuffer;ULONGoutputLength,inputLength;DbgPrint("COMM_DirectOutIo\r\n");outputLength=pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;inputLength=pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;pInputBuffer=Irp->AssociatedIrp.SystemBuffer;pOutputBuffer=NULL;if(Irp->MdlAddress)pOutputBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress,NormalPagePriority);if(pInputBuffer&&pOutputBuffer){DbgPrint("COMM_DirectOutIoUserModeMessage='%s'",pInputBuffer);RtlCopyMemory(pOutputBuffer,pInputBuffer,outputLength);*sizeofWrite=outputLength;status=STATUS_SUCCESS;}returnstatus;}//METHOD_IN_DIRECTNTSTATUSCOMM_DirectInIo(PIRPIrp,PIO_STACK_LOCATIONpIoStackIrp,UINT*sizeofWrite){NTSTATUSstatus=STATUS_UNSUCCESSFUL;PVOIDpInputBuffer,pOutputBuffer;ULONGoutputLength,inputLength;DbgPrint("COMMDirectInIo\r\n");

outputLength=pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;inputLength=pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;pInputBuffer=Irp->AssociatedIrp.SystemBuffer;pOutputBuffer=NULL;MmGetSystemAddressForMdlSafe(Irp->MdlAddress,if(Irp->MdlAddress)pOutputBufferNormalPagePriority);MmGetSystemAddressForMdlSafe(Irp->MdlAddress,if(pInputBuffer&&pOutputBuffer){DbgPrint("COMM_DirectInIoUserModeMessage='%s'",pInputBuffer);RtlCopyMemory(pOutputBuffer,pInputBuffer,outputLength);*sizeofWrite=outputLength;status=STATUS_SUCCESS;}returnstatus;}//METHOD_BUFFEREDNTSTATUSCOMM_BufferedIo(PIRPIrp,PIO_STACK_LOCATIONpIoStackIrp,UINT*sizeofWrite){NTSTATUSstatus=STATUS_UNSUCCESSFUL;PVOIDpInputBuffer,pOutputBuffer;ULONGoutputLength,inputLength;DbgPrint("COMM_BufferedIo\r\n");outputLength=pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;inputLength=pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;pInputBuffer=Irp->AssociatedIrp.SystemBuffer;pOutputBuffer=Irp->AssociatedIrp.SystemBuffer;if(pInputBuffer&&pOutputBuffer){DbgPrint("COMM_BufferedIoUserModeMessage='%s'",pInputBuffer);RtlCopyMemory(pOutputBuffer,pInputBuffer,outputLength);*sizeofWrite=outputLength;status=STATUS_SUCCESS;}returnstatus;//METHOD_NEITHERNTSTATUSCOMM_NeitherIo(PIRPIrp,PIO_STACK_LOCATIONploStacklrp,UINT*sizeofWrite){NTSTATUSstatus=STATUS_UNSUCCESSFUL;PVOIDpInputBuffer,pOutputBuffer;ULONGoutputLength,inputLength;DbgPrint("COMM_NeitherIo\r\n");outputLength=pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;inputLength=pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;pInputBuffer=pIoStackIrp->Parameters.DeviceIoControl.Type3InputBuffer;pOutputBuffer=Irp->UserBuffer;if(pInputBuffer&&pOutputBuffer){DbgPrint("COMM_NeitherIoUserModeMessage='%s'",pInputBuffer);RtlCopyMemory(pOutputBuffer,pInputBuffer,outputLength);*sizeofWrite=outputLength;status=STATUS_SUCCESS;}returnstatus;}代码比较简单,都是取得输入的数据,然后把数据直接拷贝到输出,传输给应用层。应用层的代码:procedureTfrmMain.Send_Recv_Data(AInData:String;varAOutData:String;IoctlCode:DWORD);vardwReturn:DWORD;inData:array[0..1023]ofchar;outData:array[0..1023]ofchar;beginStrPCopy(inData,AInData);ifm_hCommDevice<>0thenbeginDeviceIoControl(m_hCommDevice,IoctlCode,@inData,Length(inData),@outData,Length(outData),dwReturn,nil);AOutData:=StrPas(@outData);end;end;上面是进行发送和接受的过程。需要通信,只要如下做:代码:procedureTfrmMain.btnDirect_IN_IOClick(Sender:TObject);varoutData:String;beginSend_Recv_Data(Trim(edtDirect_in_in.Text),outData,IOCTL_COMM_DIRECT_IN_IO);edtDirect_in_out.Text:=outData;end;这是direct_in方式通信,其他通信方式类似,可以参考代码了,这里就不列举了,由于代码比较简单,我就不多说了,还是看代码吧,很好明白。(七)SSDT详解1.什么是SSDT自然,这个是我必须回答的问题。不过在此之前,请你打开命令行(cmd.exe)窗口,并输入“dir”并回车一一好了,列出了当前目录下的所有文件和子目录。那么,以程序员的视角来看,整个过程应该是这样的:由用户输入dir命令。cmd.exe获取用户输入的dir命令,在内部调用对应的Win32API函数FindFirstFile、FindNextFile和FindClose,获取当前目录下的文件和子目录。cmd.exe将文件名和子目录输出至控制台窗口,也就是返回给用户。到此为止我们可以看到,cmd.exe扮演了一个非常至关重要的角色,也就是用户与Win32API的交互。——你大概已经可以猜到,我下面要说到的SSDT亦必将扮演这个角色,这实在是一点新意都没有。没错,你猜对了。SSDT的全称是SystemServicesDescriptorTable,系统服务描述符表。这个表就是一个把ring3的Win32API和ringO的内核API联系起来的角色,下面我将以API函数OpenProcess为例说明这个联系的过程。你可以用任何反汇编工具来打开你的kernel32.dll,然后你会发现在OpenProcess中有类似这样的汇编代码:汇编代码:Callds:NtOpenProcess这就是说,OpenProcess调用了ntdll.dll的NtOpenProcess函数。那么继续反汇编之,你会发现ntdll.dll中的这个函数很短:汇编代码:moveax,7Ah〃服务号对应服务表索引movedx,7FFE0300h〃calldwordptr[edx]retn10h另外,call的一句实质是调用了KiFastSystemCall:另外,汇编代码:movedx,espsysenter上面是我的XPProfessionalsp2中ntdll.dll的反汇编结果,如果你用的是2000系统,那么可能是这个样子:汇编代码:moveax,6Ahleaedx,[esp+4]int2Ehretn10h虽然它们存在着些许不同,但都可以这么来概括:把一个数放入eax(XP是0x7A,2000是0x6A),这个数值称作系统的服务号。把参数堆栈指针(esp+4)放入edx。sysenter或int2Eh。好了,你在ring3能看到的东西就到此为止了。事实上,在ntdll.dll中的这些函数可以称作真正的NT系统服务的存根(Stub)函数。分隔ring3与ring0城里城外的这一道叹息之墙,也正是由它们打通的。接下来SSDT就要出场了,comesomemusic。2.SSDT有何用插一句先,貌似到现在为止我仍然没有讲出来SSDT是个什么东西,真正可以算是“犹抱琵琶半遮面”了。——书接上文,在你调用sysenter或int2Eh之后,Windows系统将会捕获你的这个调用,然后进入ring0层,并调用内核服务函数NtOpenProcess,这个过程如下图所示。SSDT在这个过程中所扮演的角色是至关重要的。让我们先看一看它的结构,如下图。SSDT ntoskrnl.exe(0x0000)Address1——nt!NtAcceptConnectPort・・・——・・・(0x007A)AddEess122—nt!NtOpenProcess——当程序的处理流程进入ringO之后,系统会根据服务号(eax)在SSDT这个系统服务描述符表中查找对应的表项,这个找到的表项就是系统服务函数NtOpenProcess的真正地址。之后,系统会根据这个地址调用相应的系统服务函数,并把结果返回给ntdll.dll中的NtOpenProcess。图中的“SSDT”所示即为系统服务描述符表的各个表项;右侧的“ntoskrnl.exe”则为Windows系统内核服务进程(ntoskrnl即为NTOSKerneL的缩写),它提供了相对应的各个系统服务函数。ntoskrnl.exe这个文件位于Windows的system32目录下,有兴趣的朋友可以反汇编一下。附带说两点。根据你处理器的不同,系统内核服务进程可能也是不一样的。真正运行于系统上的内核服务进程可能还有ntkrnlmp.exe、ntkrnlpa.exe这样的情况不过为了统一起见,下文仍统称这个进程为ntoskrnl.exe。另夕卜,SSDT中的各个表项也未必会全部指向ntoskrnl.exe中的服务函数,因为你机器上的杀毒监控或其它驱动程序可能会改写SSDT中的某些表项——这也就是所谓的“挂钩SSDT”——以达到它们的“主动防御”式杀毒方式或其它的特定目的。KeServiceDescriptorTable事实上,SSDT并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址、服务函数个数等等。ntoskrnl.exe中的一个导出项KeServiceDescriptorTable即是SSDT的真身,亦即它在内核中的数据实体。SSDT的数据结构定义如下:typedefstruct_tagSSDT{PVOIDpvSSDTBase;PVOIDpvServiceCounterTable;ULONGulNumberOfServices;PVOIDpvParamTableBase;}SSDT,*PSSDT; 其中,pvSSDTBase就是上面所说的“系统服务描述符表”的基地址。pvServiceCounterTable则指向另一个索引表,该表包含了每个服务表项被调用的次数;不过这个值只在CheckdBuild的内核中有效,在FreeBuild的内核中,这个值总为NULL(注:Check/Free是DDK的Build模式,如果你只使用SDK,可以简单地把它们理解为Debug/Release)。ulNumberOfServices表示当前系统所支持的服务个数。pvParamTableBase指向SSPT(SystemServiceParameterTable,即系统服务参数表),该表格包含了每个服务所需的参数字节数。下面,让我们开看看这个结构里边到底有什么。打开内核调试器(以kd为例),输入命令显示KeServiceDescriptorTable,如下。Windbg输出:lkd>ddKeServiceDescriptorTable148055ab80804e3d20000000000000011c804d9f48接下来,亦可根据基地址与服务总数来查看整个服务表的各项:Windbg输出:lkd>dd804e3d20l11c804e3d2080587691f84317aaf84317b4f84317be804e3d30f84317c8f84317d2f84317dcf84317e6804e3d408057741cf84317faf8431804f843180e804e3d50f8431818f8431822f843182cf8431836你获得的结果可能和我会有不同一一我指的是那堆以十六进制f开头的地址项,因为我的SSDT被SystemSafetyMonitor接管了,没留下几个原生的ntoskrnl.exe表项。现在是写些代码的时候了。KeServiceDescriptorTable及SSDT各个表项的读取只能在ring0层完成,于是这里我使用了内核驱动并借助DeviceIoControl来完成。其中DeviceIoControl的分发代码实现如下面的代码所示,没有什么技术含量,所以不再解释。switch(IoControlCode){caseIOCTL_GETSSDT:{—try{ProbeForWrit

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论