Windows 驱动开发基础

什么是Windows 驱动

概念

Windows驱动程序(Device Driver)是一种特殊的系统软件,它运行在操作系统的内核模式下,充当硬件设备与操作系统之间的翻译官和协调者。从技术角度来看,驱动程序是一组遵循特定接口规范的函数集合,这些函数能够直接与硬件设备进行通信,同时向操作系统提供标准化的访问接口。

与普通应用程序不同,驱动程序拥有系统的最高权限。它可以访问硬件寄存器、操作物理内存、响应硬件中断,甚至可以修改操作系统内核的数据结构。这种特权级别使得驱动程序既强大又危险------一个设计良好的驱动可以大幅提升系统性能,而一个存在漏洞的驱动则可能导致系统崩溃或成为安全攻击的入口。

作用

驱动程序在现代计算机系统中扮演着不可替代的角色。从功能角度看,它实现了硬件抽象,使得应用程序开发者无需关心底层硬件的具体实现细节。从扩展性角度看,驱动程序机制使得操作系统能够支持无限多样的硬件设备,而无需修改操作系统本身。从安全角度看,驱动程序也被广泛应用于安全软件中,用于实现系统监控、进程保护、网络过滤等功能。

驱动开发环境

  1. 下载Visual Studio(SDK)
  2. WDK 引用网址:
  3. 下载 Windows 驱动程序工具包 (WDK)

注意: WDK和SDK版本需要配套, 先装Visual Studio(SDK),再装WDK。

核心概念

内核态与用户态

Windows 将系统划分为两个特权级别:内核态(Ring 0)用户态(Ring 3)

内核态是操作系统核心与驱动程序的运行环境,代码在此拥有最高权限,可以执行特权指令、访问任意内存并直接操作硬件。用户态则是普通应用程序的运行空间,权限受到严格限制,无法直接访问硬件或内核内存,越权操作会触发保护性异常。

这种隔离机制是系统稳定性的基础:即使应用程序崩溃,也不会影响内核。当用户程序需要执行特权操作时,必须通过系统调用切换到内核态,由内核代为完成。由于该过程涉及上下文保存和权限验证,频繁的切换会带来性能开销。

驱动程序运行在内核态,这既赋予它强大的能力,也意味着必须谨慎编写,否则可能直接影响系统安全与稳定。

设备对象(Device Object)

在 Windows 驱动编程中,**设备对象(Device Object)**是驱动程序向操作系统注册的内核数据结构,代表驱动能够提供服务的一个逻辑单元。

设备对象不一定对应真实的物理硬件。一个物理设备可能需要多个设备对象来表示不同功能,例如声卡可同时创建音频输出、音频输入和 MIDI 设备对象。设备对象也可以是完全虚拟的,如用于进程间通信的虚拟设备或用于系统监控的过滤设备。

驱动程序通过调用 IoCreateDevice 函数创建设备对象。此后,设备对象成为 I/O 管理器的管理目标,所有针对该设备的 I/O 请求都会以 IRP 的形式路由到驱动程序的处理函数。设备对象中包含设备类型、特性以及关联的驱动对象等关键信息,是驱动与操作系统交互的核心结构。

符号链接(Symbolic Link)

在 Windows 驱动开发中,符号链接是连接内核空间设备与用户空间程序的桥梁。

驱动程序创建的设备对象存在于内核命名空间中,路径通常类似 \Device\MyDriver,用户态程序无法直接访问。为此,需要创建一个符号链接,将设备对象映射到用户可见的路径,如 \\.\MyDriver\DosDevices\MyDriver

这样,用户态程序便可通过 CreateFile 等标准 API 打开设备,就像操作普通文件一样。符号链接本质上是一个指针,指向真实的设备对象,从而实现内核与用户之间的通信。

IRP(I/O Request Packet)

在 Windows 内核中,**IRP(I/O Request Packet)**是表示 I/O 操作的标准数据结构。

当用户程序发起 I/O 请求时,I/O 管理器会创建一个 IRP,其中包含操作类型(读、写、控制等)、缓冲区地址和相关参数。IRP 会沿驱动程序栈逐层传递,每个驱动都可以处理或转发,直到最底层驱动完成硬件操作。

IRP 采用异步处理模式,驱动可立即返回,稍后再完成操作并通知 I/O 管理器。这种机制使 Windows 能够高效处理并发 I/O 请求,是内核 I/O 架构的核心。

Hello World 驱动程序

用户层

c++ 复制代码
#include <iostream>
#include <Windows.h>

#define wszDeviceSymbolLinkName L"\\\\.\\Yan"

#define IOCTL_DEFINE(FUNC_NUM, METHOD) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800 + FUNC_NUM, METHOD, FILE_ANY_ACCESS)
#define IOCTL_FUNCTION_1 IOCTL_DEFINE(1, METHOD_IN_DIRECT)

const int length = 0;
int main()
{
  HANDLE file = CreateFileW(wszDeviceSymbolLinkName, GENERIC_ALL, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if (file == INVALID_HANDLE_VALUE)
  {
    printf("CreateFileW failed %08X\n", GetLastError());
    return 1;
  }

  BOOL result = FALSE;
  wchar_t msg[] = L"Hello World";
  DWORD retutnLength = 0;
  printf("msg ptr: %p\n", msg);
  printf("length ptr: %p\n", &length);
  getchar();



  result = DeviceIoControl(file, IOCTL_FUNCTION_1, msg, sizeof(msg), (PVOID)&length, sizeof(length), &retutnLength, NULL);
  if (result && retutnLength == sizeof(length))
  {
    printf("msg length: %d\n", length);
  }
  else
  {
    printf("DeviceIoControl IOCTL_FUNCTION_1 failed %08X  retutnLength: %d\n", GetLastError(), retutnLength);
  }

  CloseHandle(file);

  system("pause");
  return 0;
}

驱动层

C 复制代码
#include <ntifs.h>
#include <ntstrsafe.h>
#pragma warning(disable: 4133)

#define TRACE(fmt, ...) DbgPrintEx(DPFLTR_SYSTEM_ID, DPFLTR_ERROR_LEVEL,fmt, __VA_ARGS__ )
#define wszDeviceName L"\\Device\\Yan"
#define wszDeviceSymbolLinkName L"\\DosDevices\\Yan"


#define IOCTL_DEFINE(FUNC_NUM, METHOD) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800 + FUNC_NUM, METHOD, FILE_ANY_ACCESS)
#define IOCTL_FUNCTION_1 IOCTL_DEFINE(1, METHOD_IN_DIRECT)


NTSTATUS DeviceCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{

	TRACE("DeviceCreate\n");
	Irp->IoStatus.Information = 0;
	Irp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}


NTSTATUS DevicecClose(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
	TRACE("DevicecClose\n");
	Irp->IoStatus.Information = 0;
	Irp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}

NTSTATUS DevicecUnsupport(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
	Irp->IoStatus.Information = 0;
	Irp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}

NTSTATUS DevicecControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
	TRACE("DevicecControl\n");
	ULONG ctl_code;
	ULONG ctl_method;
	ULONG inbuf_size = 0;
	ULONG outbuf_size = 0;
	PVOID inbuffer = NULL;
	PVOID outbuffer = NULL;
	NTSTATUS status = STATUS_SUCCESS;
	ULONG_PTR return_length = 0;
	PIO_STACK_LOCATION stack_location = IoGetCurrentIrpStackLocation(Irp);

	if (stack_location->MajorFunction != IRP_MJ_DEVICE_CONTROL)
	{
		status = STATUS_INVALID_DEVICE_REQUEST;
		Irp->IoStatus.Status = status;
		IoCompleteRequest(Irp, IO_NO_INCREMENT);
		return status;
	}

	ctl_code = stack_location->Parameters.DeviceIoControl.IoControlCode;
	ctl_method = METHOD_FROM_CTL_CODE(ctl_code);
	if (ctl_method == METHOD_IN_DIRECT || ctl_method == METHOD_OUT_DIRECT)
	{
		inbuffer = Irp->AssociatedIrp.SystemBuffer;
		inbuf_size = stack_location->Parameters.DeviceIoControl.InputBufferLength;
		outbuffer = Irp->MdlAddress ? MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority) : NULL;
		outbuf_size = stack_location->Parameters.DeviceIoControl.OutputBufferLength;
	}
	else
	{
		status = STATUS_INVALID_PARAMETER;
		Irp->IoStatus.Status = status;
		IoCompleteRequest(Irp, IO_NO_INCREMENT);
		return status;
	}

	//if (inbuffer == NULL || outbuffer == NULL)
	//{
	//	status = STATUS_INSUFFICIENT_RESOURCES;
	//	Irp->IoStatus.Status = status;
	//	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	//	return status;
	//}

	switch (ctl_code)
	{
	case IOCTL_FUNCTION_1:
	{
		DbgBreakPoint();
		UNICODE_STRING str;
		RtlInitUnicodeString(&str, (PWCH)inbuffer);
		TRACE("IOCTL_FUNCTION_1: %wZ\n", &str);
		*(int*)outbuffer = str.Length;
		return_length = sizeof(int);
		break;
	}
	default:
	{
		status = STATUS_NOT_IMPLEMENTED;
		return_length = 0;
		break;
	}
	}


	Irp->IoStatus.Status = status;
	Irp->IoStatus.Information = return_length;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}


VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	UNICODE_STRING usDeivceSymbolLinkName = RTL_CONSTANT_STRING(wszDeviceSymbolLinkName);
	IoDeleteSymbolicLink(&usDeivceSymbolLinkName);

	PDEVICE_OBJECT device = DriverObject->DeviceObject;
	while (device)
	{
		PDEVICE_OBJECT next = device->NextDevice;
		IoDeleteDevice(device);
		device = next;
	}
	TRACE("DriverUnload\n");
}


NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
	NTSTATUS status = STATUS_SUCCESS;
	UNICODE_STRING usDeivceName = RTL_CONSTANT_STRING(wszDeviceName);
	UNICODE_STRING usDeivceSymbolLinkName = RTL_CONSTANT_STRING(wszDeviceSymbolLinkName);
	PDEVICE_OBJECT DeivceObject = NULL;

	pDriverObject->DriverUnload = DriverUnload;

	for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i)
	{
		pDriverObject->MajorFunction[i] = DevicecUnsupport;
	}
	pDriverObject->MajorFunction[IRP_MJ_CREATE] = DeviceCreate;
	pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DevicecClose;
	pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DevicecControl;

	// 创建设备
	status = IoCreateDevice(
		pDriverObject,
		0,
		&usDeivceName,
		FILE_DEVICE_UNKNOWN,
		FILE_DEVICE_SECURE_OPEN,
		FALSE,
		&DeivceObject
	);
	if (!NT_SUCCESS(status))
	{
		TRACE("IoCreateDevice Failed status: %08X\n", status);
		goto LAB_END;
	}

	// 可以不清除该标志
	//DeivceObject->Flags |= DO_DEVICE_INITIALIZING;
	DeivceObject->Flags |= DO_DIRECT_IO;

	status = IoCreateSymbolicLink(&usDeivceSymbolLinkName, &usDeivceName);
	if (!NT_SUCCESS(status))
	{
		TRACE("IoCreateSymbolicLink Failed status: %08X\n", status);
		goto LAB_END;
	}

LAB_END:
	if (!NT_SUCCESS(status))
	{
		DriverUnload(pDriverObject);
	}

	return status;
}

验证

  1. 使用第三方驱动加载工具加载驱动

  2. 执行用户程序, 输出如下:

    复制代码
    msg ptr: 000000C17358FAA8
    length ptr: 00007FF62951BF20
    
    msg length: 0
    请按任意键继续. . .
  3. 驱动程序输出,如下:

    复制代码
    Mon Sep 29 10:19:38.800 2025 (UTC + 8:00): DeviceCreate
    Mon Sep 29 10:19:39.618 2025 (UTC + 8:00): DevicecControl
    Mon Sep 29 10:19:51.615 2025 (UTC + 8:00): IOCTL_FUNCTION_1: Hello World
    Mon Sep 29 10:19:51.623 2025 (UTC + 8:00): DevicecClose

安全防护

VirboxProtector 在保护驱动程序时采用多层次防护策略。驱动程序运行在内核态,一旦被逆向分析成功,攻击者就能了解其安全机制的实现细节,进而绕过杀毒软件监控、突破反外挂检测、或破解授权验证。因此,必须对驱动程序的核心逻辑实施深度保护。

代码虚拟化将驱动中的关键函数转换为自定义虚拟机字节码,原始的x86/x64指令被替换成只有特定虚拟机才能解释的指令序列。逆向分析时看到的不是可读的汇编代码,而是陌生的指令体系。攻击者无法直接定位IRP处理函数、回调例程等关键代码,必须先完整分析虚拟机架构,工作量远超传统驱动逆向。

代码混淆通过控制流平坦化、虚假分支、指令替换等技术打乱驱动结构。简单的权限检查被展开成复杂跳转网络,清晰的函数调用变成间接跳转,原本几行的逻辑膨胀成上百行等效代码。即使能够阅读反汇编,也难以理解驱动的真实工作流程。

相关推荐
cellurw3 小时前
Day52 串口通信原理与IMX6ULL UART驱动开发
驱动开发
caibixyy3 小时前
Kafka Windows 安装启动与 YAML 配置全解析
windows·kafka
Bruce_Liuxiaowei3 小时前
Kerberos协议深度解析:工作原理与安全实践
运维·windows·安全·网络安全
Blue桃之夭夭4 小时前
WebStorm 快捷键大全(Windows / macOS 双平台对照)
windows·macos·webstorm
平凡灵感码头5 小时前
STM32 程序内存分布详解
stm32·单片机·嵌入式硬件
QUST-Learn3D5 小时前
C++单头文件实现windows进程间通信(基于命名管道)
c++·windows·单片机
会跑的葫芦怪5 小时前
Gin 框架令牌桶限流实战指南
驱动开发·gin
沉木渡香6 小时前
VSCode中Java开发环境配置的三个层级(Windows版)1-3
java·windows·vscode
btzhy6 小时前
STM32单片机:基本定时器应用:精确定时(STM32L4xx)
stm32·单片机·嵌入式硬件·基本定时器应用:精确定时