Windows 驱动入门

Windows驱动层是我一直想学的东西,最近也在看软件调试。

如果不了解驱动层的开发,就很难把驱动层的逆向学好。

目标很简单,先把驱动的基本安装代码实现,再把R0层与R3层通信的方式实现

最后通过R3层的程序能通过直接调用R0层的接口来实现R3层操作R0层。

DbgView配置

涉及到开发DbgView是必不可少的一个工具

windows7需要添加一个注册表项才能接受到DbgView的消息:

1
2
3
4
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager]
"Debug Print Filter"=dword:0000000f

配置好后重启即可。

WDK + VS2012

这里用VS2012是因为win10对驱动开发不友好,选择Windows7做开发环境,VS2010代码提示有点问题,上次学C++开发CLR程序的时候根本没提示。

因此选用了VS2012,目前用的蛮好,现在我的开发环境一直用VS2012,那么WDK只能选择8.0了,8.1版本的WDK没办法支持VS2012。

还有我比较喜欢本地版本的MSDN,不想在WEB上查询代码,这些VS2012都能搞定,给大家推荐一下。

最简单的驱动程序

开发驱动,也是要从Hello,world开始入门的,这就来写一个最简单的驱动程序。

选用WDM空项目

上MSDN,找DriverEntry的代码,拷贝复制

编写一段入门程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <ntddk.h>

DRIVER_UNLOAD Unload;

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath){

DbgPrint("驱动加载成功!");

DriverObject->DriverUnload = Unload;


return STATUS_SUCCESS;
}


VOID Unload(struct _DRIVER_OBJECT *DriverObject){
DbgPrint("驱动卸载成功!");

}

将警告视为错误 这个选项禁用

编译成功,驱动成功被注册。

使用DbgView查看输出

第一步:打开选项

第二步:安装启动卸载驱动

总结一下,就是驱动要有个入口,类似于exe程序的main,然后入口的第一个参数是一个引用,你需要将引用的卸载地址指向卸载驱动的函数地址。

至此,HelloWorld之路已经走完了,下面要开始学习关于通信的方式了。

DriverEntry特性

DriverEntry()一共有 2 个参数

  • PDRIVER_OBJECT DriverObject,指向驱动程序对象的指针。
  • PUNICODE_STRING RegistryPath,驱动程序的服务主键,在DriverEntry() 返回后则会释放,需要用时记得保存。

在DriverEntry中,一般需要做一下几件事情:

  • 设置驱动卸载例程
  • 注册 IRP 的派遣函数
  • 创建设备对象

驱动卸载例程与 IRP 派遣函数都是对驱动对象设置的。

驱动通信的方式有很多:

  • 共享内存
  • 共享事件
  • IOCTL宏
  • ReadFile() 或 WriteFile()

今天这里学习的使用IOCTL宏来进行通信。

Win32程序使用 DeviceIoControl() 与驱动进行通信。

驱动程序与 I/O 管理器通信,使用的是 IRP,即 I/O 请求包。

IRP 分为2部分:

  • 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(用户层)应用程序调用kernel32.dll导出的DeviceIoControl函数后,会调用到ntdll.dll导出的NtDeviceIoControlFile函数,进而调用到ntoskrnl.exe提供的服务函数NtDeviceIo ControlFile,该函数会将I/O请求转化为IRP包,并发送到对应驱动的派遣例程函数中,其机制类似于Windows的消息机制。

IRP是一个很复杂的数据结构,IRP两个基本的属性:

  • MajorFunction
  • MinorFunction

分别记录IRP的主类型和子类型。

操作系统根据MajorFunction将IRP 分发给派遣函数,在派遣函数中还可以判断IRP属于哪种MinorFunction。

DriverEntry有个函数指针数组MajorFunction.都是派遣函数地址。