随着 windows 系统的更新迭代,windows 驱动开发技术也是不断的升级:从最早期的 VXD(Virtual X Driver)(已废弃)到 windows 2000 上推出的 WDM(Windows Driver Model)驱动模型,随后从 windows vista 推出的 WDF(Windows Driver Foudation)驱动模型,沿用至今;WDF 是 WDM 的升级版,并且在一定程度上兼容,WDF 是微软目前推荐的驱动开发模型。
WDF 还可以细分为内核模式 KMDF(Kernel-Mode Driver Framework) 和用户模式 UMDF(User-Mode Driver Framework),顾名思义 UMDF 将受到更多的限制从而换来更高的操作系统稳定性,其二进制扩展名为*.dll
;UMDF 和 KMDF 开发基本相同,本文这里仅介绍使用更广泛的 KMDF 开发。
本文实验环境
windows 10 专业版 x64 1909
Visual Studio 2019
SDK 10.0.19041.685
WDK 10.0.19041.685
首先配置Visual Studio 2019
的 C/C++ 开发环境(https://visualstudio.microsoft.com/),按 Visual Studio 官方教程,自动下载安装「使用C++的桌面开发」,其中 SDK 默认为10.0.19041.0
:
开发软件需要 SDK(Software Development Kit),而开发 windows 驱动则需要 WDK(Windows Driver Kit);现在我们来配置 WDK 环境,从官网(https://learn.microsoft.com/zh-cn/windows-hardware/drivers/download-the-wdk)下载 WDK 在线安装包(版本必须和 SDK 一致),按如下进行安装:
对于 WDF 驱动模型其开发环境叫 WDK(Windows Driver Kit)
对于 WDM 驱动模型其开发环境叫 DDK(Driver Development Kit)
安装完毕后,其窗口会默认勾选为Visual Studio
安装 WDK 扩展插件,按照指导进行安装即可,随后我们可以在Visual Studio
的创建项目页面,就看到 KMDF/UMDF 等选项,表示 windows 驱动开发环境配置成功。
windows 驱动开发环境可能会受操作系统、Visual Studio、SDK、WDK 的版本影响,配置过程需要多留心这些环节,如遇见问题可以参考如下版本信息 https://learn.microsoft.com/en-us/windows-hardware/drivers/other-wdk-downloads。
根据官方教程,我们在Visual Studio
中创建空的KMDF
项目,并在其中创建Driver.c
文件,编写代码如下:
#include <ntddk.h>
#include <wdf.h>
DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd;
NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
// NTSTATUS variable to record success or failure
NTSTATUS status = STATUS_SUCCESS;
// Allocate the driver configuration object
WDF_DRIVER_CONFIG config;
// Print "Hello World" for DriverEntry
KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: DriverEntry\n"));
// Initialize the driver configuration object to register the
// entry point for the EvtDeviceAdd callback, KmdfHelloWorldEvtDeviceAdd
WDF_DRIVER_CONFIG_INIT(&config,
KmdfHelloWorldEvtDeviceAdd
);
// Finally, create the driver object
status = WdfDriverCreate(DriverObject,
RegistryPath,
WDF_NO_OBJECT_ATTRIBUTES,
&config,
WDF_NO_HANDLE
);
return status;
}
NTSTATUS
KmdfHelloWorldEvtDeviceAdd(
_In_ WDFDRIVER Driver,
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
// We're not using the driver object,
// so we need to mark it as unreferenced
UNREFERENCED_PARAMETER(Driver);
NTSTATUS status;
// Allocate the device object
WDFDEVICE hDevice;
// Print "Hello World"
KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd\n"));
// Create the device object
status = WdfDeviceCreate(&DeviceInit,
WDF_NO_OBJECT_ATTRIBUTES,
&hDevice
);
return status;
}
在「配置管理器」中设置为Debug/x64
,编译生成项目发现如下错误:
Visual Studio
默认开启了缓解 Spectre 攻击的机制,可在 VS 安装器中选择安装指定的支持库,我们实验环境下可以直接关闭该功能,在项目属性-C/C++-代码生成-Spectre Mitigation
设置为Disable
:
随后便可以成功编译,在[src]/x64/Debug/
下,可以看到生成的二进制文件KmdfHelloWorld.sys
。
驱动程序一般以服务(service)或设备(device)的方式运行工作,这点和普通应用程序不同,所以不能像应用程序那样进行调试和测试。官方指导中提供一种基于双机调试环境的驱动调试方法,这种方式较为繁琐,我们这里进行简要介绍;想了解更方便的本机调试驱动程序的小伙伴,可跳转至「0x04 本机调试驱动程序」。
按照官方指导,我们将驱动程序作为设备进行运行调试,在此之前需要再提供一台主机作为被调试机(debugee
),驱动程序将在被调试机上(debugee
)进行部署和测试,而本台主机即作为开发主机同时作为调试机(debugger
),如下:
首先在被调试机(debugee
)上也安装上 WDK 环境,随后在 WDK 的安装目录下运行该工具WDK Test Target Setup
,默认路径:C:\Program Files (x86)\Windows Kits\10\Remote\x64\WDK Test Target Setup x64-x64_en-us.msi
;在之后调试机(debugger
)中的Visual Studio
将连接被调试机(debugee
)的WDK Test Target Setup
的工具,自动完成双机调试环境的配置。
在开发主机上(debugger
)初始化被调试机的相关信息,在项目属性-Driver Install-Deployment
中添加新设备,我们这里使用网络双机调试的方式,默认参数即可:
随后将自动完成配置,如下:
在Visual Studio
中将被调试机(debugee
)添加完毕后,在如下窗口选择该主机并设置驱动的硬件 ID 为Root\KmdfHelloWorld
,如下:
配置完成后,我们在Visual Studio
菜单中生成-部署解决方案
,驱动程序将自动部署在被调试机上(debugee
)并进行测试运行:
在被调试机(debugee
)上我们在设备管理器中可以看到KmdfHelloWorld
已经成功部署了:
如果想调试驱动程序,则可以使用 WinDBG 依据以上的双机调试环境对驱动程序进行调试。
官方提供的驱动程序部署和测试方法,虽然有效的隔离开发环境和调试环境,但实在是过于繁琐了,更不用说其中双机调试环境下的各种问题。而驱动程序还可以以服务的方式进行运行,我们通过这种方式可以更加方便的在本机调试驱动程序。
在日常安全工作中,我更喜欢使用这种方式,因为大多数情况我只需要工作在内核层的驱动代码,而不关心其是否是完整的 windows 驱动设备,这种方式能帮助我快速进行安全验证工作。
创建 KMDF 项目并编写代码如下:
#include <ntddk.h>
#include <wdf.h>
VOID OnUnload(IN PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "test: unload driver\n"));
}
NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);
KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "test: driver entry\n"));
DriverObject->DriverUnload = OnUnload;
return STATUS_SUCCESS;
}
在「配置管理器」中设置为Debug/x64
,编译生成项目。
在运行测试前,我们需要在本机(即开发主机)上打开测试模式(重启生效),使得操作系统可以加载我们编译的驱动程序,使用管理员权限打开 powershell:
# 打开测试模式
bcdedit /set testsigning on
重启主机后,使用管理员权限打开 powershell,通过sc.exe
命令为驱动程序创建服务(命令详解请参考:https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/sc-create):
# 创建服务名为 test,类型为 kernel,启动方式为 demand 的服务,并指定驱动程序路径
# (注意参数等号后必须有一个空格)
sc.exe create test type= kernel start= demand binPath= C:\Users\john\Desktop\workspace\kmdf_test\x64\Debug\kmdf_test\kmdf_test.sys
# 使用 queryex 查看创建的服务信息
sc.exe queryex test
创建服务如下:
随后便可以使用sc.exe
命令启动驱动程序运行,并使用DebugView
查看调试输出(需要勾选Capture Kernel
和Enable Verbose Kernel Output
才能看到输出):
# 启动运行驱动程序
sc.exe start test
# 停止运行驱动程序
sc.exe stop test
运行如下:
当我们更新了驱动代码、编译项目后,可以再次start/stop
这个服务,便可以快捷的进行驱动程序代码的测试和调试。
https://learn.microsoft.com/zh-cn/windows-hardware/drivers/gettingstarted/
https://github.com/microsoft/Windows-driver-samples</https://github.com/microsoft/Windows-driver-samples>
https://visualstudio.microsoft.com/
https://learn.microsoft.com/zh-cn/windows-hardware/drivers/download-the-wdk</https://learn.microsoft.com/zh-cn/windows-hardware/drivers/download-the-wdk>
https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/sc-create</https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/sc-create>