配置好驱动测试环境后,就可以正式编写驱动了。市面上讲解驱动开发的书籍汗牛充栋,但讲得较为太复杂,让初学者不好理解。本文从一个简单的hello,world驱动(驱动模板)讲起,力求讲解得简单明了,让大家好理解。

 

本文主角:

1. DbgView。DbgView是查看程序调试输出的工具

下载地址:http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx

2.KmdMgr。KmdMgr 是一个由俄国人编写的驱动加载工具。比起国内那些乱七八糟的驱动加载工具,它的特点是可以与驱动进行通信(虽然无法设置 I/O 缓冲区)。下载地址:

https://www.assembla.com/code/L2h/subversion/nodes/LowLevel/KmdManager.exe?_for mat=raw&rev=1

3. WIN64AST。作者自行开发的64ARK类工具。在本章中用来查看驱动是否加载成功。在后续章节还有其他的用途。下载地址:www.win64ast.com

4.WIN64UDL。作者自行开发的驱动加载工具,能在正常模式下加载没有签名的驱动。

 

编写驱动:  以下是一个我写的WIN64驱动模板

//【0】包含的头文件,可以加入系统或自己定义的头文件
#include <ntddk.h>
#include <windef.h>
#include <stdlib.h>
//【1】定义符号链接,一般来说修改为驱动的名字即可
#define DEVICE_NAME L"\\Device\\KrnlHW64"
#define LINK_NAME L"\\DosDevices\\KrnlHW64"
#define LINK_GLOBAL_NAME L"\\DosDevices\\Global\\KrnlHW64"
//【2】定义驱动功能号和名字,提供接口给应用程序调用
#define IOCTL_IO_TEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800,
METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_SAY_HELLO CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801,
    METHOD_BUFFERED, FILE_ANY_ACCESS)
    //【3】驱动卸载的处理例程
VOID DriverUnload(PDRIVER_OBJECT pDriverObj)
{
    UNICODE_STRING strLink;
    DbgPrint("[KrnlHW64]DriverUnload\n");
    RtlInitUnicodeString(&strLink, LINK_NAME);
    IoDeleteSymbolicLink(&strLink);
    IoDeleteDevice(pDriverObj->DeviceObject);
}
//【4】 IRP_MJ_CREATE对应的处理例程,一般不用管它
NTSTATUS DispatchCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
    DbgPrint("[KrnlHW64]DispatchCreate\n");
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}
//【5】 IRP_MJ_CLOSE对应的处理例程,一般不用管它
NTSTATUS DispatchClose(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
    DbgPrint("[KrnlHW64]DispatchClose\n");
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}
//【6】 IRP_MJ_DEVICE_CONTROL对应的处理例程,驱动最重要的函数之一,一般走正常途径调用驱动功能的程序,都会经过这个函数
NTSTATUS DispatchIoctl(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
    NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
    PIO_STACK_LOCATION pIrpStack;
    ULONG uIoControlCode;
    PVOID pIoBuffer;
    ULONG uInSize;
    ULONG uOutSize;
    DbgPrint("[KrnlHW64]DispatchIoctl\n");
    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    //控制码
    uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
    //输入输出缓冲区
    pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;
    //输入区域大小
    uInSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
    //输出区域大小
    uOutSize = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
    switch(uIoControlCode)
    {
        //在这里加入接口
    case IOCTL_IO_TEST:
        {
            DWORD dw=0;
            //获得输入的内容
            memcpy(&dw,pIoBuffer,sizeof(DWORD));
            //使用输入的内容
            dw++;
            //输出处理的结果
            memcpy(pIoBuffer,&dw,sizeof(DWORD));
            //处理成功,返回非STATUS_SUCCESS会让DeviveIoControl返回失败
            status = STATUS_SUCCESS;
            break;
        }
    case IOCTL_SAY_HELLO:
        {
            DbgPrint("[KrnlHW64]IOCTL_SAY_HELLO\n");
            status = STATUS_SUCCESS;
            break;
        }
    }
    if(status == STATUS_SUCCESS)
        pIrp->IoStatus.Information = uOutSize;
    else
        pIrp->IoStatus.Information = 0;
    pIrp->IoStatus.Status = status;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return status;
}
//【7】驱动加载的处理例程,里面进行了驱动的初始化工作
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING
    pRegistryString)
{
    NTSTATUS status = STATUS_SUCCESS;
    UNICODE_STRING ustrLinkName;
    UNICODE_STRING ustrDevName;
    PDEVICE_OBJECT pDevObj;
    //初始化驱动例程
    pDriverObj->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;
    pDriverObj->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
    pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl;
    pDriverObj->DriverUnload = DriverUnload;
    RtlInitUnicodeString(&ustrDevName, DEVICE_NAME);
    status = IoCreateDevice(pDriverObj, 0, &ustrDevName, FILE_DEVICE_UNKNOWN,
        0, FALSE, &pDevObj);
    if(!NT_SUCCESS(status)) return status;
    if(IoIsWdmVersionAvailable(1, 0x10))
        RtlInitUnicodeString(&ustrLinkName, LINK_GLOBAL_NAME);
    else
        RtlInitUnicodeString(&ustrLinkName, LINK_NAME);
    //创建符号链接
    status = IoCreateSymbolicLink(&ustrLinkName, &ustrDevName);
    if(!NT_SUCCESS(status))
    {
        IoDeleteDevice(pDevObj);
        return status;
    }
    //走到这里驱动实际上已经初始化完成,下面添加的是功能初始化的代码
    DbgPrint("[KrnlHW64]DriverEntry\n");
    return STATUS_SUCCESS;
}

编译驱动:

1.打开『x64 Free Build Environment』:

2.切换到源码目录(假设源码目录是:z:\sys),并输入BUILD编译:

3.如果看到『1 executable built』字眼,则证明编译成功。

4.驱动的编译跟目录下的source文件有关系,比如本例中,它的内容如下(注意不要手贱把空行去掉了,否则可能会导致无法编译):

TARGETNAME=KrnlHW64  <-驱动的文件的名称,一般来说修改这个就行了 
TARGETTYPE=DRIVER  <-编译的类型 
TARGETPATH=obj 
 
INCLUDES=.\ 
 
SOURCES = MyDriver.c  <-多个C文件时,把所有C文件的名称分成多行写 

测试驱动前的准备:

1.以管理员权限运行DBGVIEW

2.把 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session
Manager\Debug

Print Filter的Default值修改为ffffffff

3.打开DBGVIEW并把以下选项全部勾上:

标准的驱动测试方法:

1.打开虚拟机,进入双机调试的环境(忘记了就参考上节课的内容)。

2.运行KmdMgr.exe,把SYS拖动到文本框里。

3.点击“Register”“Run”按钮,看看输出是否提示成功。如果成功会有类似的输出:

4.运行WIN64AST,点击内核模块,查看驱动是否已经存在于内核里了:

5.在CODE处输入222004(为什么是222004而不是801?这个后面会讲到,这里先卖一个关子。但这个数值可以使用calc_ctl_code.exe算出来,既输入801,可以输出222004),点击“I/O
Control”
按钮,如果成功会有类似的输出:

6. 点击“Unregister”“Stop”按钮,如果成功会有类似的输出:

很显然,用标准方法测试一个驱动是很麻烦且很耗时的。双机调试非常占用系统资源,虽然我的电脑配置较好(2600K+16GB内存),但是在操作虚拟机时,仍然感到了明显的卡顿。下面介绍一种用特殊工具测试驱动的方法,无需双机调试,甚至无需使用虚拟机。

 

WIN64UDL测试驱动:

1.运行WIN64UDL

2.把驱动文件拖进WIN64UDL里,然后按下ENTER加载。

3.再按一次ENTER卸载驱动。