驱动程序入门篇

驱动程序分为两类: 一个是 Kernel(内核) 模式驱动,另一个是 Windows (用户窗口层)模式驱动。

这两种模式本质是相同,但细节不同。本文介绍的是内核模式驱动和驱动程序的安装与使用。

驱动程序同普通的 .exe,.dll 一样,都属于 PE 文件,而且都有一个入口函数。但 .exe 中,入口函数是 main() / WinMain() 和 Unicode 的 wmain() / wWinmain(),.dll 的入口函数则可有可无,它是 DllMain()。

所以驱动程序也有入口函数,而且是必须的,它是 DriverEntry() 函数。再次提示,它是必须的! 因为 I/O 管理器会首先调用驱动程序的DriverEntry() 函数,它的作用就像 DllMain() --完成一些初始化工作。

虽然我们有时候把 DriverEntry() 比作 main(),但二者在本质上不同,DriverEntry() 的生命周期非常短,其作用仅是将内核文件镜像加载到系统中时进行驱动初始化,调用结束后驱动程序的其他部分依旧存在,并不随它而终止。

所以我们一般可把 DriverEntry() 称为“入口函数”,而不可称为“主函数”。因此作为内核驱动来说,它没有一个明确的退出点,这应该是 atexit 无法在内核中实现的原因吧。什么是 atexit() 函数呢?它是注册终止函数(即main执行结束后调用的函数) !

DriverEntry()一共有 2 个参数:

           1)PDRIVER_OBJECT DriverObject,指向驱动程序对象的指针。我们操作驱动程序,全靠它,它是由 I/O 管理器传递进来的;

           2)PUNICODE_STRING RegistryPath,驱动程序的服务主键,这个参数的使用并不多,但要注意,在DriverEntry() 返回后,它可能
会消失,所以如果需要使用,记住先要保存下来

DriverEntry() 等内核函数的返回值一般是 NTSTATUS 类型的。使用宏NT_SUCCESS(NTSTATUS status)可检测返回状态是否成功。它是一个 ULONG 值(无符号长整型,另一种表示形式:Unsigned long  变量名),具体的定义,请参见 DDK 中的 NTSTATUS.H 头文件,里边有详细的定义。

在DriverEntry中,一般需要做一下几件事情: 设置驱动卸载例程、 注册 IRP 的派遣函数、 创建设备对象等!并且驱动卸载例程与 IRP 派遣函数都是对驱动对象设置的。

既然要写驱动程序就需要确定如何来与驱动程序通信,常用的共享内存,共享事件,IOCTL宏,或者直接 ReadFile() 或 WriteFile() 进行读写,在本文里我就采用一种简单的、但又很常用的 IOCTL 宏,它依赖的 IRP 派遣例程 IRP_MJ_DEVICE_CONTROL,Win32程序使用 DeviceIoControl() 与驱动进行通信,根据不同的 IOCTL 宏,输出不同的调试信息。

驱动程序与 I/O 管理器通信,使用的是 IRP,即 I/O 请求包。IRP 分为2部分:IRP 首部;IRP堆栈。

在驱动程序中,IRP 派遣例程起着很重要的作用,每个 IRP 派遣例程,几乎都有对应的 Win32 API 函数。

下面介绍 IRP 派遣例程 :

名称 描述 调用者
IRP_MJ_CREATE 请求一个句柄 CreateFile
IRP_MJ_CLEANUP 在关闭句柄时取消悬挂的IRP CloseHandle
IRP_MJ_CLOSE 关闭句柄 CloseHandle
IRP_MJ_READ 从设备得到数据 ReadFile
IRP_MJ_WRITE 传送数据到设备 WriteFile
IRP_MJ_DEVICE_CONTROL  控制操作(利用IOCTL宏) DeviceIoControl
IRP_MJ_INTERNAL_DEVICE_CONTROL 控制操作(只能被内核调用) N/A
IRP_MJ_QUERY_INFORMATION 得到文件的长度 GetFileSize
IRP_MJ_SET_INFORMATION 设置文件的长度 SetFileSize
IRP_MJ_FLUSH_BUFFERS 写输出缓冲区或丢弃输入缓冲区 FlushFileBuffers\FlushConsoleInputBuffer \PurgeComm
IRP_MJ_SHUTDOWN 系统关闭 InitiateSystemShutdown

提到派遣例程,必须理解 IRP(I/O Request Package),即"输入/输出请求包"这个重要数据结构的概念。Ring3(用户层)通过 DeviceIoControl 等函数向驱动发出 I/O 请求后,在内核中由操作系统将 I/O 请求包转化为 IRP 的数据结构,并"派遣"到对应驱动的派遣函数中,如图下图所示。

Ring3(用户层)应用程序调用kernel32.dll导出的DeviceIoControl函数后,会调用到ntdll.dll导出的NtDeviceIoControlFile函数,进而调用到系统内核模块提供的服务函数NtDeviceIo ControlFile,该函数会将I/O请求转化为IRP包,并发送到对应驱动的派遣例程函数中。对于其他I/O相关函数,如CreateFile、ReadFile、WriteFile、GetFileSize、SetFileSize、CloseHandle等也是如此。

                                                            新建位图图像

如上图,从 Ring3 (用户层)的 I/O 请求到内核的 IRP 结构的请求包

一个 IRP包 该发往驱动的哪个派遣例程函数,是由 IRP 结构中的 MajorFunction (IRP主类型)属性决定的,MajorFunction 属性的值是一系列宏,具体可在网上查询。

IRP 首部信息如下:
                      IO_STATUS_BLOCK IoStatus                 //包含 I/O 请求的状态
                      PVOID AssociatedIrp.SystemBuffer       //  如果执行缓冲区 I/O,这个指针指向系统缓冲区
                      PMDL MdlAddress                        //  如果直接 I/O,这个指针指向用户缓冲区的存储器描述符表
                      PVOID UserBuffer                         // I/O 缓冲区的用户空间地址


IRP 堆栈信息如下:

                      UCHAR MajorFunction              //(主要类型) 指示 IRP_MJ_XXX派遣例程
                      UCHAR MinorFunction              //(IRP 的子类型) 同上,一般文件系统和 SCSI 驱动程序使用它
                      union Parameters  {   //MajorFunction的联合类型
                             struct Read                       //IRP_MJ_READ的参数
                             ULONG Length
                             ULONG Key
                             LARGE_INTEGER ByteOffset
                             struct Write                      //IRP_MJ_WRITE的参数
                             ULONG Length
                             ULONG Key
                             LARGE_INTEGER ByteOffset
                             struct DeviceIoControl            //IRP_MJ_DEVICE_CONTROL和IRP_MJ_INTERNAL_DEVICE_CONTROL的参数
                             ULONG OutputBufferLength
                             ULONG InputBufferLength
                             ULONG IoControlCode
                             PVOID Type3InputBuffer
                      } 
                      PDEVICE_OBJECT DeviceObject       //请求的目标设备对象的指针
                      PFILE_OBJECT FileObject           //请求的目标文件对象的指针,如果有的话操作 IRP。

对于不同的 IRP 函数,操作也是不同的:有的只操作 IRP 首部;有的只操作 IRP 堆栈;还有操作 IRP 整体。

下面是一些常用的函数:
IRP整体:                                                                                                                   

名称 描述 调用者
IoStartPacket 发送IRP到Start I/O例程 Dispatch (派遣)
IoCompleteRequest 表示所有的处理完成 DpcForIsr (连接中断和注册)
IoStartNextPacket 发送下一个IRP到Start I/O例程 DpcForIsr
IoCallDriver 发送IRP请求 Dispatch
IoAllocateIrp 请求另外的IRP Dispatch
IoFreeIrp 释放驱动程序分配的IRP I/O Completion (I/0完成)

IRP堆栈:

名称 描述 调用者
IoGetCurrentIrpStackLocation 得到调用者堆栈的指针 Dispatch
IoMarkIrpPending 为进一步的处理标记调用者I/O堆栈 Dispatch
IoGetNextIrpStackLocation 得到下一个驱动程序的I/O堆栈的指针 Dispatch
IoSetNextIrpStackLocation 将I/O堆栈指针压入堆栈 Dispatch

 

/************************************************************************************************
*                                 驱动程序与应用程的通讯示例                                                                                                     *
*************************************************************************************************
* 应用程序类型:ControlAppliaction                                                                                                                             *
* 应用程序名称:Appinformation                                                                                                                                 *
* 驱  动 名 称:AppDriver                                                                                                                                           *
* 符号链接名称:AppSymbolLink                                                                                                                                 *
*************************************************************************************************
* 简述:编写一个驱动程序与应用程之间的通信讯实例,让应用程序输入数据,进而发送 I/O 请求,当                                             *
* 系统接的 I/O 管理器在接收到应用层的设备读写请求后,将请求封装为一个 IRP 请求(包括 IRP 头部                                            *
* 和 IRP STACK_LOCATINO 数组),然后根据 IRP 的类型发送到相应类型派遣例程,派遣例程再把接                                           *
* 受到的 IRP发送到对应的设备的设备栈的最顶层的那个设备驱动(如果顶层的设备对象的派遣函数结                                              *
* 束了IRP的请求,则这次IO请求结束,如果没有将 IRP 请求结束,那么操作系统将IRP转发到设备栈的                                           *
* 下一层设备处理。如果这个设备的派遣函数依然没结束该IRP请求,则继续向下层设备进行转发)。                                               *
* IO请求结束后驱动会把处理后的数据返回应用程序。                                                                                                         *
*************************************************************************************************/

先介绍一下驱动程序和应用程序将要使用到的 Win32 API 函数:

1、DriverEntry(): 驱动程序的入口函数,它将内核文件镜像加载到系统中时进行驱动初始化。

2、DriverUnload():负责删除在DriverEntry中创建的设备对象并且将设备对象所关联的符号链接删除,另外,DriverUnload还负责对一些资源进行回收。

3、IoDeleteDevice():此 API 函数为删除设备对象的函数,只有一个参数,即为妖卸载的设备名称。

4、IoDeleteSymbolicLink() : 此 API 函数为删除符号链接的函数,只有一个参数,即为符号链接名称。

5、IoCreateDevice():此 API 函数创建设备对象供驱动程序使用。包含七个参数。

6、RtlInitUnicodeString():此 API 函数初始化一个Unicode字符串,主要用来实例化设备名称或符号链接名称。

7、IoCreateSymbolicLink():此 API 函数主要是对已实例化的设备名称和符号链接名称进行绑定。参数为符名和设备名

8、IoGetCurrentIrpStackLocation():此 API 函数向调用者返回一个 IRP 当前堆栈位置的指针,参数为设备对象和 IRP 指针(PIRP)。

9、IoCompleteRequest():此 API 函数表示调用方已完成所有 I/O 请求处理操作,并将给定的 IRP 传给 I/O 管理器。无返回值。

10、CTL_CODE() 宏:此宏为系统提供的,在wdm中定义的,用于创建一个唯一的32位系统I/O控制代码,这个控制代码包括4部分组成。

11、CreateFile(): 此函数创建或打开(文件,文件流,目录,磁盘,控制台缓冲区)等设备,并返回一个用来访问这些对象的句柄 。

12、DeviceIoControl():  此函数直接发送一个控制代码到指定的设备驱动程序,使相应的设备来执行相应的操作。

13、CloseHandle():此函数关闭一个打开的对象句柄,该对象句柄可以是线程句柄,也可以是进程、信号量等其他内核对象的句柄 。  

============================================================================================================

下面请看驱动程序代码:

首先用 C++ 创建一个空白的“ 控制台应用程序”项目:然后新建一个源文件:Appinformation.cpp

代码如下:

#include <iostream>
#include <tchar.h>
#include <Windows.h>
#include "InputBuffer.h"
using std::cout;
using std::endl;

int _tmain(int argc, _TCHAR* argv[])
{    
    /************************************函数说明*********************************************
    * 函数名:CreateFile                                                                     *
    * 类型:Win32 API函数                                                                    *
    * 作用:这是一多功能的函数,可打开或创建如:控制台,通信资源,目录(只读打开),                    *
    * 磁盘驱动器,文件,邮槽,管道等对象,并返回可访问的句柄:。                                     *
    * 参数说明:                                                                             *
    * LPCTSTR lpFileName : 普通文件名或者设备文件名                                            *
    * DWORD dwDesiredAccess : 访问对象的权限,希望以哪种方式打开,建议两者兼备。                    *
    * DWORD dwShareMode : 共享模式,建议两者兼备。                                             *
    * LPSECURITY_ATTRIBUTES : 用来设定一个指向是否由子进程返回的句柄可以被继承                     *
    * DWORD dwCreationDisposition : 表示判断文件存在或不存在,按需求如何创建                      *
    * DWORD dwFlagsAndAttributes : 文件标志或设备属性                                         *
    * HANDLE hTemplateFile :模板文件,给创建的文件提供文件属性和扩展属性,一般为空                   *
    * 返回值: 获取设备的句柄                                                                   *
    *****************************************************************************************/
    HANDLE  DeviceHandle = CreateFile(
        L"\\\\.\\AppSymbolLink",              //\\??\\设备符号链接名称,注意这里要用 L 宽字符转换
        GENERIC_READ | GENERIC_WRITE,    
        FILE_SHARE_READ | FILE_SHARE_WRITE,   
        NULL,                             
        OPEN_EXISTING,                        //枚举:OPEN_EXISTING 表示文件必须已经存在。
        FILE_ATTRIBUTE_NORMAL,                 //此枚举表示文件没有其他属性集。
        NULL);                                 //模板文件,一般为空
    if (DeviceHandle == INVALID_HANDLE_VALUE){
        printf("获取文件或设备句柄失败!!");
        getchar();                            //读取清除缓冲区字符
        return -1;
    }

        printf("获取驱动程序句柄成功 \n");
        int a=10, b=20, c=30,e=0,f=0,g=0;
        InputBuffer(DeviceHandle, a, b, c);
        for (int i = 0; i < 3; i++)
        {
            switch (i){
            case 0:{
                       e = OtnC[i];
            }
            case 1:{
                       f = OtnC[i];
            }
            case 2:{
                       g = OtnC[i];
            }
            } //End switch (i)
        }  //End for 

    printf("a=%d,b=%d,c=%d\n",e, f, g);    
    CloseHandle(DeviceHandle);            //过程完成后,最好关闭句柄    
    cout << "驱动通讯完成" << endl;
    system("pause");
}

 

好了,按设备的符号链接打开了设备,并得到了它的句柄,有了句柄,我们就能更好的和它进行互动了!!

如果想和他通信,就需要发送通讯口令了,我们把发送口令的函数它写在头文件:InputBuffer.h中。

#pragma once
#include <iostream>
#include <Windows.h>
#include "SetCtlCode.h"

int OtnC[3] = { 0 };  //整形全局数组变量

/************************************函数说明****************************************
* 函数名:InputBuffer                                                               *
* 类型:自定义无返回值函数                                                             *
* 作用:这个函数用于给控制代码函数提供参数,也为了其函数调用获取返回值。                       *                                                                                
* 参数说明:                                                                         *
* DeviceHandle : 设备句柄,用于指定通信的设备                                          *
* a : 通讯数据。                                                                    *
* b : 通讯数据。                                                                    *
* c : 通讯数据                                                                      *
* 返回值: 无 ,因为返回值已赋给了全局变量                                                *
************************************************************************************/

void InputBuffer(HANDLE DeviceHandle, int a, int b, int c){
    int ItnC[3] = { a, b, c }; ULONG wtWrite;
    int *InBuffer = ItnC;
    int *OutBuffer = OtnC;

    /************************************函数说明********************************
    * 函数名:DeviceIoControl                                                   *
    * 类型:Win32 API函数                                                       *
    * 作用:发送控制代码到指定设备驱动程序。                                         *    
    * 参数说明:                                                                *
    * hDevice Long:设备句柄。用于指定通信的设备                                    *
    * dwIoControlCode Long:应用程序调用驱动程序的控制命令,就是IOCTL_XXX IOCTLs。   *
    * lpInBuffer Any:应用程序传递给驱动程序的数据缓冲区地址。                        *
    * nInBufferSize Long:应用程序传递给驱动程序的数据缓冲区大小,字节数。             *
    * lpOutBuffer Any:驱动程序返回给应用程序的数据缓冲区地址。                       *
    * nOutBufferSize Long:驱动程序返回给应用程序的数据缓冲区大小,字节数。            *
    * lpBytesReturned Long:驱动程序实际返回给应用程序的数据字节数地址。              *
    * lpOverlapped OVERLAPPED:针对同步操作,请用ByVal As Long传递零值              *
    * 返回值: 非0成功,0失败                                                      *
    *****************************************************************************/

    DeviceIoControl(DeviceHandle, SetCtlCodeA, InBuffer, sizeof(InBuffer)*3, OutBuffer, sizeof(OutBuffer)* 3, &wtWrite, NULL);           
    printf("DeviceIoControl 发送控制代码完成 \n");
    int e = 0, f = 0, g = 0;
    for (int i = 0; i < 3; i++)
    {
        OtnC[i] = OutBuffer[i];
    }
    return ;
}

看到没! DeviceIoControl 函数中的第二个参数就需要提供口令了,这里专业的说法叫,“I/O控制码”(也叫输入输出控制码),我们把这控制代码卸载另一个头文件中:SetCtlCode.h

#include <winioctl.h>
/************************************宏说明***********************************
* 宏名:CTL_CODE                                                             *
* 类型:Win32 宏命令                                                          *
* 作用:用于创建一个唯一的32位系统I/O控制代码。                                   *
* 参数说明:                                                                 *
* DeviceType:设备类型 ,枚举如(FILE_DEVICE_UNKNOWN)未知设备                   *
* Function:  设备唯一功能标识码,有效范围:32768-65535                          *
* Method:    I/O访问内存的使用方式,枚举,一般用METHOD_BUFFERED。                *
* Access:    设备访问限制,,枚举,一般用FILE_ANY_ACCESS                        *
* 返回值:    无                                                              *
*****************************************************************************/
//可以只写一个
#define SetCtlCodeA CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define SetCtlCodeB CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS)

好了,到此,一个发送 I/O 请求包的 控制台应用程序就创建并编写完毕,下面来开始我们的驱动编写:

先创建一个 WDM 工程,然后添加一个头文件,名为:AppDriver.cpp

然后开入编写入口函数:

#ifdef _cplusplus
extern "C"
{
#endif
#include <ntddk.h>
#include "AddDevice.h"
#include "SendIRP.h"
#ifdef _cplusplus
}
#endif

#define PAGEDCODE cod_seg("PAGE")             //将以下函数载入到分页内存中执行
#pragma PAGEDCODE                              //载入到分页内存中执行
VOID Unload(PDRIVER_OBJECT pDriverObject);  
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistrPath)
{
    CreateMyDevice(pDriverObject);                                      //调用自定义的 CreateMyDevice 来创建设备对象

    //注册派遣函数,与IRP 请求包的数据类型一一对应
    pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Send_Irp; // 发送I/O管理器和其他系统组件、其他内核模式驱动
    pDriverObject->MajorFunction[IRP_MJ_CREATE] = Send_Irp;         // 创建设备,CreateFile会产生此IRP,返回一个句柄给用户应用程序
    pDriverObject->MajorFunction[IRP_MJ_READ] = Send_Irp;         // 读取设备内容,ReadFile会产生此IRP
    pDriverObject->MajorFunction[IRP_MJ_WRITE] = Send_Irp;       // 改写设备内容,WriteFile时会产生此IRP
    pDriverObject->MajorFunction[IRP_MJ_CLOSE] = Send_Irp;       // 关闭保留的通往目标设备对象的句柄,CloseHandle会产生此IRP
    pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = Send_Irp;    // 清除用户模式应用程序的目标设备对象的句柄,CloseHandle会产生此IRP    
    pDriverObject->DriverUnload = Unload;                               //调用卸载函数    
    KdPrint(("AppDriver 驱动加载成功!"));
    return STATUS_SUCCESS;
}

VOID Unload(PDRIVER_OBJECT pDriverObject)
{    
    pDriverObject->DeviceObject = AppDevice;    //指向设备对象
    IoDeleteDevice(AppDevice);                  //卸载设备对象
    IoDeleteSymbolicLink(&AppSymbolLinkName);   //卸载符号链接
    KdPrint(("驱动卸载成功!"));
}

入口函数 DriverEntry 的任务包含了 初始化系统代码为驱动指令,还包含了对自定义 Unload 函数的调用和 IRP 请求包的派遣。

先我们利用 IoCreateDevice  API函数来创建设备驱动,我把它写在另一个头文件中:AddDevice.h

#pragma once            //仅编译一次,用途:常出现在头文件中,因为同一头文件会在许多源文件中多次引用,如果没有指定编译一次,则编译时出现重定义错误。
#ifdef _cplusplus
extern "C"
{
#endif 
#include <ntddk.h>
#ifdef _cplusplus
}
#endif 

static PDEVICE_OBJECT AppDevice;                          //设备对象全局变量
static UNICODE_STRING AppDeviceName, AppSymbolLinkName;   //设备名称和符号链接名称全局变量

//创建设备
#define INITCODE cod_seg("INIT")                               // 初始化的时候载入内存,然后可以从内存中卸载掉
#pragma INITCODE
NTSTATUS CreateMyDevice(PDRIVER_OBJECT pDriverObject){
    NTSTATUS Status;                                                   //返回值局部变量        
    RtlInitUnicodeString(&AppDeviceName, L"\\Device\\AppDriver");      //初始化设备名称
    RtlInitUnicodeString(&AppSymbolLinkName, L"\\??\\AppSymbolLink");  //初始化符号链接名称
        
    //调用 IoCreateDevice API 函数创建设备对象
    Status=IoCreateDevice(pDriverObject, 0, &AppDeviceName, FILE_DEVICE_UNKNOWN, NULL, TRUE, &AppDevice);

    
    if (!NT_SUCCESS(Status)){                  //判断返回值,看是否创建成功
        switch (Status){                        //如果没有成功,返回枚举错误消息
        case STATUS_INSUFFICIENT_RESOURCES:
            KdPrint(("资源部足!!"));
            break;
        case STATUS_OBJECT_NAME_EXISTS:
            KdPrint(("指定对象名存在!!"));
            break;
        case STATUS_OBJECT_NAME_COLLISION:
            KdPrint(("对象名称冲突!!"));
            break;
        }        
    }

    //设备创建成功后,就设置它的缓冲区读写方式,这里为(读写)
    AppDevice->Flags |= DO_BUFFERED_IO; 

    //调用 IoCreateSymbolicLink API 函数对设备名称和符号链接名称进行绑定
    Status=IoCreateSymbolicLink(&AppSymbolLinkName, &AppDeviceName);
    
    //判断返回值,看是否绑定成功
    if (NTSTATUS(Status) != STATUS_SUCCESS){              //判断返回值,看是否绑定成功
        IoDeleteDevice(AppDevice);                        //如果未成功,调用IoDeleteDevice 函数删除设备
        KdPrint(("设备因符号链接绑定失败而被删除!!"));
    }
    KdPrint(("设备创建完成,设备已成功加载"));
    return Status;                                        //如果成功就返回成功的宏
}

到此,我们就真正创建了一个驱动,没错确实创建了,但像个人样,但却没有灵魂啊,没有灵魂就没有意识,没有意识就是死的!!!

所以,我们就得给他构造自主的潜微的意识,让它能对别人的呼叫会有所回应!!这里就要用到 IoGetCurrentStackLocation 这个API函数来接受被人发出的信息(IRP 请求包)派遣,从哪里获取呢,这里所有的信息都存放在 Stack 堆栈上,所以此时就需要在堆栈上获得: 我们再次新建一个头文件SendIRP,在这里面编写:

#ifdef _cplusplus
extern "C"
{
#endif 
#include <ntddk.h>
#include "SetCtlCode.h"
#ifdef _cplusplus
}
#endif 

#define INITCODE cod_seg("INIT")                                 // 初始化的时候载入内存,然后可以从内存中卸载掉
#pragma INITCODE 
NTSTATUS Send_Irp(PDEVICE_OBJECT pDeviceObject, PIRP pIrp){
      NTSTATUS Status; PIO_STACK_LOCATION Stack;                 //定义返回值和堆栈变量
      Stack=IoGetCurrentIrpStackLocation(pIrp);                  //获取当前 IRP 在堆栈上的位置指针      
      UCHAR Irp_Type = Stack->MajorFunction;                     //获取 IRP 在堆栈中的类型
      KdPrint(("Irp_Type=%d \n", Irp_Type));

      //对 IRP 的类型进行判断,进行不同的处理
      switch (Irp_Type){
          case IRP_MJ_DEVICE_CONTROL:{
                                       KdPrint(("In to IRP_MJ_DEVICE_CONTROL \n"));
                                       ULONG CTL_CODE = Stack->Parameters.DeviceIoControl.IoControlCode;
                                       ULONG InBufferLength = Stack->Parameters.DeviceIoControl.InputBufferLength;
                                       ULONG OutBufferLength = Stack->Parameters.DeviceIoControl.OutputBufferLength;
                                       int *OutBuffer = (int *)pIrp->AssociatedIrp.SystemBuffer;
                                       KdPrint(("Get CTL_CODE,CTL_CODE=%d \n",CTL_CODE));
                                       KdPrint(("InBufferLength=%d \n" , InBufferLength));
                                       KdPrint(("OutBufferLength=%d \n", OutBufferLength));
                                       KdPrint(("*OutBuffer=%d \n", OutBuffer));
                                       switch (CTL_CODE){
                                           case Add_Ctl_Code:{
                                                                 KdPrint(("In to Add_Ctl_Code \n"));
                                                                 int i, a, b, c;
                                                                 _asm{                      //用汇编语句来(读取)缓冲区指针中的数据
                                                                     mov eax,OutBuffer
                                                                         mov ebx,[eax]
                                                                         mov a,ebx
                                                                         mov ebx, [eax+4]
                                                                         mov b,ebx
                                                                         mov ebx, [eax+8]
                                                                         mov c,ebx
                                                                 }
                                                                 KdPrint(("a=%d,b=%d,c=%d \n",a,b,c));
                                                                 i = a + b + c;
                                                                 KdPrint(("i=a + b + c,i=%d \n", i));
                                                                  a =a*i, b=b*i, c=c*i;
                                                                 _asm{                      //用汇编语句来(填充)缓冲区指针
                                                                     mov eax,a
                                                                         mov ebx,OutBuffer
                                                                         mov [ebx],eax
                                                                         mov eax, b
                                                                         mov [ebx + 4],eax                                                                                                                                    
                                                                         mov eax,c
                                                                         mov [ebx + 8],eax                                                                         
                                                                 }
                                                                 pIrp->IoStatus.Information = OutBufferLength;
                                                                 pIrp->IoStatus.Status = STATUS_SUCCESS;
                                                                 IoCompleteRequest(pIrp, IO_NO_INCREMENT);
                                                                 KdPrint(("Leave the Add_Ctl_Code \n", i));
                                                                 break;
                                           }
                                           case Sub_Ctl_Code:{
                                                                 break;
                                           }
                                       }  //End (switch (CTL_CODE))
                                       break;
          }
          case IRP_MJ_CREATE:{
                                 break;
          }
          case IRP_MJ_READ:{
                               break;
          }
          case IRP_MJ_WRITE:{
                                break;
          }
          case IRP_MJ_CLOSE:{
                                break;
          }
          case IRP_MJ_CLEANUP:{
                                  break;
          }

      }  // End (switch (Irp_Type))

      return STATUS_SUCCESS;

}  //End  Send_Irp Function

此断代码主要对在堆栈中获取其它应用程序发送的 I/O 请求包的 IRP 派遣,驱动程序再更具 IRP 的派遣类型就行不同的处理,然后,得到请求数据,并对它进行了重新计算,然后通过汇编语句对计算后的数据进行反馈给堆栈中,让发送I/O 请求包 的应用程序进行接受数。

接受 或发送 I/O 请求包 都同样需用 I/O 控制代码,才能完成!所以我们再来编写I/O 控制代码,把它写另一个头文件中:SetCtlCode.h

#define Add_Ctl_Code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define Sub_Ctl_Code CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS)

由于驱动已经包含了 IOCTL 宏,所以这里不必再包含<winioctl.h>头文件了。

到此整个应用程序和驱动整形就编写完成! 我们可以在虚拟机制中通过 DriverMonitor .exe 和 DbgView工具进行加载驱动并进行查看,然后打开AppInformation.exe 控制台应用程序进行发送 I/O请求包,就能到接受驱动计算过的数据了!

posted on 2015-11-26 18:52  I_am  阅读(6915)  评论(0编辑  收藏  举报

导航