在上一篇中,我简单介绍了驱动开发的基础知识,这一篇中,将介绍编写一个NT空壳驱动(驱动基础框架)
一、驱动入口
驱动程序的入口点是DriverEntry,此函数的原型是:
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegistryPath)
参数1:系统发来的驱动对象的指针
参数2:一个内核字符串对象的指针,是驱动的服务名称。(NT驱动是以服务的形式安装的,关于服务的知识可以看我以前的服务开发和SCManager的相关博文)一般这个参数是用于在加载时判断服务名称是不是驱动程序所接受的(如果有这方面的需要)。关于内核字符串我将在之后的博文中讲。
返回值:返回内核状态码,STATUS_SUCCESS表示成功
二、注册驱动卸载函数
在驱动入口中可以注册驱动卸载函数,如果不注册,驱动程序一旦加载就无法卸载(rootkit病毒、杀毒软件的驱动无法卸载除了hook卸载函数外也可以用这种方法阻止卸载)
注册的方法为:
pDriverObject->DriverUnload = DriverUnload;
驱动卸载函数原型:
extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
应该在这个函数中完成驱动程序的清理工作,以免内存泄露等问题。
三、派遣函数
派遣函数类似于win32应用程序的回调函数,只不过派遣函数的调用是并发的,因为调用派遣函数的线程不是固定的,win32程序的回调函数是在一个线程里工作的,是串行的。
通过这些代码注册派遣函数:
pDriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRoutine;
这里注册了创建、关闭、读、写四种类型的IRP的派遣函数。除了这四个以外,还有个非常常用的是IRP_MJ_DEVICE_CONTROL,应用程序调用DeviceIoControl时I/O管理器发送此IRP。
IRP有很多,比如:(这里面还有一些是给WDM驱动用于即插即和电源管理的)
IRP_MJ_CLEANUP
IRP_MJ_CLOSE
IRP_MJ_CREATE
IRP_MJ_DEVICE_CONTROL
IRP_MJ_FILE_SYSTEM_CONTROL
IRP_MJ_FLUSH_BUFFERS
IRP_MJ_INTERNAL_DEVICE_CONTROL
IRP_MJ_PNP
IRP_MJ_POWER
IRP_MJ_QUERY_INFORMATION
IRP_MJ_READ
IRP_MJ_SET_INFORMATION
IRP_MJ_SHUTDOWN
IRP_MJ_SYSTEM_CONTROL
IRP_MJ_WRITE
派遣函数中,我们什么也不做,因为对于这个驱动空壳而言,没有要做的,将IRP完成,直接返回成功。
其中关于IRP请求的我会在下面的博文中讲。
extern "C" NTSTATUS DispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return status;
}
完整代码:
#include
extern "C" VOID DriverUnload(IN PDRIVER_OBJECT pDriverObject);
extern "C" NTSTATUS DispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp);
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegistryPath)//驱动入口
{
DbgPrint("DriverEntry\r\n");
pDriverObject->DriverUnload = DriverUnload;//注册卸载函数
pDriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchRoutine;//注册派遣函数
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRoutine;
return STATUS_SUCCESS;
}
extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
DbgPrint("DriverUnload\r\n");
}
extern "C" NTSTATUS DispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);//完成IRP请求
return status;
}
其中DbgPrint是输出调试信息。
四、驱动程序的编译
对于WDK8,是作为visual studio的一个可选组件发布的,下载对应版本的VS即可直接在IDE里编写,编译。
但是我很不习惯用IDE开发驱动,我还是喜欢控制台编译,可以用WDK7,选择x86 Free的环境。(解释一下,free相当于win32中的release版本,checked相当于debug版本,明白了吧。)
编译64位驱动选amd64的即可
进入源码目录,注意要写这两个文件。
makefile文件,这个是不变的,从微软的例子或网上源码里随便找一个就行,内容如下:
!INCLUDE $(NTMAKEENV)\makefile.def
Sources文件,指定工程名称,源码文件等,这里如下:
TARGETNAME=MyDriver
TARGETTYPE=DRIVER
TARGETPATH=OBJ
INCLUDES=$(BASEDIR)\inc;\
$(BASEDIR)\inc\ddk;\
SOURCES=Driver.cpp\
不用我解释吧。
回到编译环境,cd到源码目录,调用build程序,如果没有错误,就编译得到一个sys文件,sys也是PE文件之一,其结构和exe,dll等等一样,都是PE结构,这个就是编译出来的驱动程序了。
下面加载这个驱动,由于加载驱动调试很危险,动不动就蓝屏,所以建议大家在虚拟机中测试,如果在本机测试(博主以前就这么干,往事不堪回首啊)后果很严重。。
可以用DebugView看到驱动输出的调试信息。
另外64位系统上有驱动程序强制签名,因此除了买签名外可以开启测试模式,并自己颁发证书签名即可加载。
这一篇我是用的工具安装的驱动,并用sc命令启动的驱动,在下一篇中,我将会介绍如何用win32编程的方式,用应用程序安装和加载驱动。