《C++黑客编程解密》04 - 内核驱动开发
在windows下,驱动不仅用于控制硬件设备,也可以创建虚拟设备,也可以和具体设备无关。可以通过windows提供的接口很方便地对内核扩展。驱动模块加载到内核后就可以工作在和操作系统平级地平台上运行。
在安装vs之后还要再安装wdk https://docs.microsoft.com/zh-cn/windows-hardware/drivers/download-the-wdk
注意:win11使用vs2022与相应wdk,win10使用vs2019和相应wdk。
win10 下运行程序可能会报错,提示要修复漏洞,到vs installer 中再下载几个组件:
编译程序前需要做:
- 移除项目自动生成的.inf文件
- 配置属性 - C/C++ -常规
警告等级 设置为 等级3
将警告视为错误 设置为 否 - 配置属性 - 链接器 -常规
将链接器警告视为错误 设置为 否 - 配置属性 - Driver Signing - General
Sign Mode 设置为 Off。
驱动版 Hello World
开发驱动时不再使用 main WinMain,而是使用 DriveEntry 作为入口函数
// DriverEntry[WDK kernel] 定义,除此以外还有很多 DriverEntry 函数的定义
NTSTATUS
DriverEntry(
__in struct _DRIVER_OBJECT *DriverObject, // 一个指向驱动对象结构体地指针
__in PUNICODE_STRING RegistryPath // 一个UNICODE字符串,指向此驱动负责的注册表
)
{ ... }
helloworld的例子:
#include <ntddk.h>
VOID DriverUnload(PDRIVER_OBJECT pDriverOjbect)
{
KdPrint(("DriverUnload Routine!\r\n"));
}
NTSTATUS DriverEntry(
PDRIVER_OBJECT pDriverObject,
PUNICODE_STRING pRegistryPath)
{
KdPrint(("%S\r\n", pDriverObject->DriverName.Buffer));
pDriverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
DriverObject 结构体:
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
//
// The following links all of the devices created by a single driver
// together on a list, and the Flags word provides an extensible flag
// location for driver objects.
//
PDEVICE_OBJECT DeviceObject; // 指向驱动程序创建的设备对象的指针
ULONG Flags;
//
// The following section describes where the driver is loaded. The count
// field is used to count the number of times the driver has had its
// registered reinitialization routine invoked.
//
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension; // 指向驱动程序扩展的指针
//
// The driver name field is used by the error log thread
// determine the name of the driver that an I/O request is/was bound.
//
UNICODE_STRING DriverName;
//
// The following section is for registry support. This is a pointer
// to the path to the hardware information in the registry
//
PUNICODE_STRING HardwareDatabase; // 指向 注册表中硬件配置信息的 \Registry\Machine\Hardware 路径的指针
//
// The following section contains the optional pointer to an array of
// alternate entry points to a driver for "fast I/O" support. Fast I/O
// is performed by invoking the driver routine directly with separate
// parameters, rather than using the standard IRP call mechanism. Note
// that these functions may only be used for synchronous I/O, and when
// the file is cached.
//
PFAST_IO_DISPATCH FastIoDispatch; // 指向定义驱动程序快速 I/O 入口点的结构的指针。 此成员仅由 FSD 和网络传输驱动程序使用。
//
// The following section describes the entry points to this particular
// driver. Note that the major function dispatch table must be the last
// field in the object so that it remains extensible.
//
PDRIVER_INITIALIZE DriverInit; // DriverEntry 例程的入口点,由 I/O 管理器设置
PDRIVER_STARTIO DriverStartIo; // 驱动程序的 StartIo 例程的入口点(如果有)(由驱动程序初始化时 DriverEntry 例程设置)。 如果驱动程序没有 StartIo 例程 ,则此成员为 NULL。
PDRIVER_UNLOAD DriverUnload; // 卸载驱动时的回调函数 typedef VOID DRIVER_UNLOAD ( _In_ struct _DRIVER_OBJECT *DriverObject );
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; // 一个调度表,由驱动程序的 DispatchXxx 例程的入口点数组组成。 数组的索引值是表示IRP_MJ_ IRP 主要函数代码的索引值。 每个驱动程序必须在此数组中为驱动程序处理的 IRP_MJ_XXX 请求设置入口点
} DRIVER_OBJECT;
驱动程序装载
将驱动程序加载为一个服务,卸载时直接删除服务。常见的Windows驱动程序分为两类,一类时不支持即插即用的NT式驱动,另一类是支持即插即用的WDM式驱动。NT式驱动程序安装是基于服务的,WDM式驱动安装是基于INF文件的。
void Load(char* szDriverPath)
{
char szFileName[256];
_splitpath(szDriverPath, NULL, NULL, szFileName, NULL);
// 打开服务控制管理器
SC_HANDLE hScm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
// 创建驱动对应的服务
SC_HANDLE hService = CreateService(hScm, szFileName, szFileName,
SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START,
SERVICE_ERROR_IGNORE, szDriverPath, NULL, NULL, NULL, NULL, NULL);
// 启动服务
StartService(hService, NULL, NULL);
CloseServiceHandle(hService);
CloseServiceHandle(hScm);
}
void Unload(char* szDriverPath)
{
char szFileName[256];
_splitpath(szDriverPath, NULL, NULL, szFileName, NULL);
// 打开服务控制管理器
SC_HANDLE hScm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
// 获得服务句柄
SC_HANDLE hService = OpenService(hScm, szFileName, SERVICE_ALL_ACCESS);
// 关闭服务
SERVICE_STATUS status;
ControlService(hService, SERVICE_CONTROL_STOP, &status);
// 删除服务
DeleteService(hService);
// 关闭句柄
CloseServiceHandle(hService);
}
内核下文件操作
内核中不能使用用户层的win32api函数,需要使用内存层对应函数。
文件创建或打开:
再应用层文件完整路径为"c:\\a.txt",内核层描述方式为"\\?\\c:\\a.txt";
符号链接在用户模式下以"\\\\.\\"开头,内核模式下以"\\??\\"或"\\DosDevices\\"开头。
// 可以创建和打开
NTSYSAPI
NTSTATUS
NTAPI
ZwCreateFile(
_Out_ PHANDLE FileHandle, // 返回文件打开的句柄
_In_ ACCESS_MASK DesiredAccess, // 打开文件的操作 GENERIC_READ GENERIC_WRITE
_In_ POBJECT_ATTRIBUTES ObjectAttributes, // 指向包含文件名的结构体的指针
_Out_ PIO_STATUS_BLOCK IoStatusBlock, // 指向接收操作结果的指针
_In_opt_ PLARGE_INTEGER AllocationSize, // 指定文件初始化大小的64位整数
_In_ ULONG FileAttributes, // 通常 FILE_ATTRIBUTE_NORMAL
_In_ ULONG ShareAccess, // 文件共享方式 FILE_SHARE_READ FILE_SHARE_WRITE FILE_SHARE_DELETE
_In_ ULONG CreateDisposition, // FILE_CREATE FILE_OPEN FILE_OPEN_IF
_In_ ULONG CreateOptions, // 通常使用 FILE_SYNCHRONOUS_IO_NONALERT 表示文件是同步操作
_In_reads_bytes_opt_(EaLength) PVOID EaBuffer, // 一般为NULL
_In_ ULONG EaLength // NULL
);
// 第三个参数指向的结构体
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor; // Points to type SECURITY_DESCRIPTOR
PVOID SecurityQualityOfService; // Points to type SECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES;
// 一般使用 InitializeObjectAttributes 宏进行初始化
//++
//
// VOID
// InitializeObjectAttributes(
// _Out_ POBJECT_ATTRIBUTES p, 指向结构体的指针
// _In_ PUNICODE_STRING n, 对象名称,用UNICODE_STRING 描述,对于 ZwCreateFile 该处指定文件名
// _In_ ULONG a, 一般设置为 OBJ_CASE_INSENSITIVE 意味名字字符串不区分大小写
// _In_ HANDLE r, 一般NULL
// _In_ PSECURITY_DESCRIPTOR s 一般NULL
// )
//
//--
#define InitializeObjectAttributes( p, n, a, r, s ) { \
(p)->Length = sizeof( OBJECT_ATTRIBUTES ); \
(p)->RootDirectory = r; \
(p)->Attributes = a; \
(p)->ObjectName = n; \
(p)->SecurityDescriptor = s; \
(p)->SecurityQualityOfService = NULL; \
}
// 内核对宽字符串的封装,使用KdPrint 输出: KdPrint(("%wZ", &uniString));
// 初始化方法:RtlInitUnicodeString(&FooU, L"Foo");
typedef struct _UNICODE_STRING {
USHORT Length; // 字符串长度
USHORT MaximumLength; // 缓存区长度
PWSTR Buffer; // 缓冲区指针
#endif // MIDL_PASS
} UNICODE_STRING;
用于打开的简化函数
NTSYSAPI
NTSTATUS
NTAPI
ZwOpenFile(
_Out_ PHANDLE FileHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_ ULONG ShareAccess,
_In_ ULONG OpenOptions
);
关闭句柄:
NTSYSAPI
NTSTATUS
NTAPI
ZwClose(
_In_ HANDLE Handle
);
相关操作
- ZwReadFile
- ZwWriteFile
- ZwQueryInformationFile
- ZwSetInformationFile
NTSYSAPI
NTSTATUS
NTAPI
ZwQueryInformationFile(
_In_ HANDLE FileHandle, // 被打开文件句柄
_Out_ PIO_STATUS_BLOCK IoStatusBlock, // 返回设置的状态
_Out_writes_bytes_(Length) PVOID FileInformation, // 依据FileInformationClass 的不同而不同
_In_ ULONG Length, // 数据长度,第3个参数大小
_In_ FILE_INFORMATION_CLASS FileInformationClass // 描述需获取的属性类型
);
NTSYSAPI
NTSTATUS
NTAPI
ZwSetInformationFile(
_In_ HANDLE FileHandle,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_reads_bytes_(Length) PVOID FileInformation, // 设置参数
_In_ ULONG Length,
_In_ FILE_INFORMATION_CLASS FileInformationClass
);
// FILE_INFORMATION_CLASS 的常用值有三种类型 FileStandardInformation FileBasicInformation FilePositionInformation,每种类型对应不同结构体
NTSYSAPI
NTSTATUS
NTAPI
ZwReadFile(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event, // 用于异步完成读取,一般为NULL
_In_opt_ PIO_APC_ROUTINE ApcRoutine, // 回调例程,异步完成时读取,一般UNULL
_In_opt_ PVOID ApcContext, // NULL
_Out_ PIO_STATUS_BLOCK IoStatusBlock, // 记录读取操作的状态
_Out_writes_bytes_(Length) PVOID Buffer, // 保存内容的缓冲区
_In_ ULONG Length, // 准备读取内容的字节数
_In_opt_ PLARGE_INTEGER ByteOffset, // 指定读取内容的偏移地址
_In_opt_ PULONG Key // 附加信息 一般NULL
);
NTSYSAPI
NTSTATUS
NTAPI
ZwWriteFile(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_reads_bytes_(Length) PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
);
内存管理函数
NTKERNELAPI
PVOID
ExAllocatePool (
// 枚举值 NonPagedPool非分页内存:不会被交换到文件中的虚拟内存 PagedPool分页内存:可以被交换到文件中的虚拟内存
__drv_strictTypeMatch(__drv_typeExpr) _In_ POOL_TYPE PoolType,
_In_ SIZE_T NumberOfBytes // 要分配的内存大小
);
#define RtlFillMemory(Destination,Length,Fill) memset((Destination),(Fill),(Length))
NTKERNELAPI
VOID
NTAPI
ExFreePool (
_Pre_notnull_ __drv_freesMem(Mem) PVOID P
);
例子:
查看代码
#include <ntddk.h>
#define FILENAME L"\\??\\c:\\a.txt"
#define BUFFERLEN 10
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
}
VOID CreateFileTest()
{
NTSTATUS status = STATUS_SUCCESS;
OBJECT_ATTRIBUTES ObjAttribute;
IO_STATUS_BLOCK ioStatusBlock;
UNICODE_STRING uniFile;
HANDLE hFile = NULL;
RtlInitUnicodeString(&uniFile, FILENAME);
// 初始化一个对象属性
InitializeObjectAttributes(&ObjAttribute,
&uniFile,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
// 创建文件
status = ZwCreateFile(&hFile,
GENERIC_READ | GENERIC_WRITE,
&ObjAttribute,
&ioStatusBlock,
0,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
if ( NT_SUCCESS(status) )
{
KdPrint(("File Create ok ! \r\n"));
}
else
{
KdPrint(("File Create faild ! \r\n"));
}
ZwClose(hFile);
}
VOID OpenFileTest()
{
IO_STATUS_BLOCK ioStatusBlock;
OBJECT_ATTRIBUTES ObjAttribute;
HANDLE hFile;
UNICODE_STRING uniFileName;
NTSTATUS status;
FILE_STANDARD_INFORMATION fsi;
FILE_POSITION_INFORMATION fpi;
PVOID Buffer = NULL;
RtlInitUnicodeString(&uniFileName, FILENAME);
// 初始化一个对象属性
InitializeObjectAttributes(&ObjAttribute,
&uniFileName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
// 打开文件
status = ZwOpenFile(&hFile,
GENERIC_READ | GENERIC_WRITE,
&ObjAttribute,
&ioStatusBlock,
FILE_SHARE_READ,
FILE_SYNCHRONOUS_IO_NONALERT);
if ( NT_SUCCESS(status) )
{
KdPrint(("OpenFile Successfully ! \r\n"));
}
else
{
KdPrint(("OpenFile UnSuccessfully ! \r\n"));
return ;
}
// 获取文件属性
status = ZwQueryInformationFile(hFile,
&ioStatusBlock,
&fsi,
sizeof(FILE_STANDARD_INFORMATION),
FileStandardInformation);
// 输出新创建的文件是否是目录
KdPrint(("Is Directory %d \r\n", fsi.Directory));
// 分配空间
Buffer = ExAllocatePool(PagedPool, BUFFERLEN);
if ( Buffer == NULL )
{
ZwClose(hFile);
return ;
}
// 填充值为
RtlFillMemory(Buffer, BUFFERLEN, 0x61);
// 写文件
status = ZwWriteFile(hFile,
NULL,
NULL,
NULL,
&ioStatusBlock,
Buffer,
BUFFERLEN,
0,
NULL);
if ( NT_SUCCESS(status) )
{
KdPrint(("ZwWriteFile Successfully ! \r\n"));
}
// 释放申请的空间
ExFreePool(Buffer);
// 获取文件属性
status = ZwQueryInformationFile(hFile,
&ioStatusBlock,
&fsi,
sizeof(FILE_STANDARD_INFORMATION),
FileStandardInformation);
// 输出新创建的文件是否是目录
KdPrint(("FileSize = %d \r\n", (LONG)fsi.EndOfFile.QuadPart));
Buffer = ExAllocatePool(PagedPool, (fsi.EndOfFile.QuadPart * 2));
if ( Buffer == NULL )
{
KdPrint(("ExAllocatePool UnSuccessfully ! \r\n"));
ZwClose(hFile);
return ;
}
fpi.CurrentByteOffset.QuadPart = 0;
// 设置文件指针
status = ZwSetInformationFile(hFile,
&ioStatusBlock, &fpi,
sizeof(FILE_POSITION_INFORMATION),
FilePositionInformation);
// 读取文件内容
status = ZwReadFile(hFile, NULL, NULL, NULL,
&ioStatusBlock, Buffer,
(LONG)fsi.EndOfFile.QuadPart,
NULL, NULL);
if ( NT_SUCCESS(status) )
{
KdPrint(("ZwReadFile Successfully ! \r\n"));
}
KdPrint(("%s", Buffer));
ExFreePool(Buffer);
ZwClose(hFile);
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,
PUNICODE_STRING pRegistryPath)
{
pDriverObject->DriverUnload = DriverUnload;
CreateFileTest();
OpenFileTest();
return STATUS_SUCCESS;
}
注册表操作
- ZwCreateKey
- ZwOpenKey
- ZwSetValueKey
- ZwQueryValueKey
// 用于创建和打开
NTSYSAPI
NTSTATUS
NTAPI
ZwCreateKey(
_Out_ PHANDLE KeyHandle,
_In_ ACCESS_MASK DesiredAccess, // 访问权限 KEY_ALL_ACCESS
_In_ POBJECT_ATTRIBUTES ObjectAttributes, // 用于保存要创建的子键
_Reserved_ ULONG TitleIndex, // 0
_In_opt_ PUNICODE_STRING Class, // NULL
_In_ ULONG CreateOptions, // 一般 REG_OPTION_NON_VOLATILE
_Out_opt_ PULONG Disposition // 返回创建成功还是打开成功
);
NTSYSAPI
NTSTATUS
NTAPI
ZwOpenKey(
_Out_ PHANDLE KeyHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes
);
NTSYSAPI
NTSTATUS
NTAPI
ZwSetValueKey(
_In_ HANDLE KeyHandle,
_In_ PUNICODE_STRING ValueName, // 新建或修改的键名
_In_opt_ ULONG TitleIndex, // 一般为0
_In_ ULONG Type, // 键值类型,REG_SZ REG_DWORD REG_MULTI_SZ
_In_reads_bytes_opt_(DataSize) PVOID Data, // 写入键值的值
_In_ ULONG DataSize // 记录数据的大小
);
NTSYSAPI
NTSTATUS
NTAPI
ZwQueryValueKey(
_In_ HANDLE KeyHandle,
_In_ PUNICODE_STRING ValueName, // 键名
_In_ KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, // 查询方式
_Out_writes_bytes_to_opt_(Length, *ResultLength) PVOID KeyValueInformation, // 根据前者选择不同类别
_In_ ULONG Length, // 要查数据长度
_Out_ PULONG ResultLength // 实际查询数据长度
);
查看代码
#include <ntddk.h>
#define REG_PATH L"\\Registry\\Machine\\Software\\Microsoft\\Windows\\CurrentVersion\\run\\"
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
}
VOID CreateKey()
{
UNICODE_STRING uniRegPath;
OBJECT_ATTRIBUTES objAttributes;
NTSTATUS nStatus;
HANDLE hRegistry;
ULONG ulResult;
RtlInitUnicodeString(&uniRegPath, REG_PATH);
InitializeObjectAttributes(&objAttributes,
&uniRegPath,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
// 创建注册表项
nStatus = ZwCreateKey(&hRegistry,
KEY_ALL_ACCESS,
&objAttributes,
0,
NULL,
REG_OPTION_NON_VOLATILE,
&ulResult);
if ( NT_SUCCESS(nStatus) )
{
KdPrint(("ZwCreateKey Successfully ! \r\n"));
}
else
{
KdPrint(("ZwCreateKey Unsuccessfully ! \r\n"));
}
// 关闭注册表句柄
ZwClose(hRegistry);
}
VOID QueryAndSetKey(HANDLE hRegistry)
{
UNICODE_STRING uniValueName;
NTSTATUS nStatus;
PWCHAR pValue = L"test";
PKEY_VALUE_PARTIAL_INFORMATION pKeyValuePartialClass;
ULONG ulResult;
RtlInitUnicodeString(&uniValueName, L"test");
// 添加注册表键值
nStatus = ZwSetValueKey(hRegistry,
&uniValueName,
0,
REG_SZ,
pValue,
wcslen(pValue) * 2 + sizeof(WCHAR));
if ( NT_SUCCESS(nStatus) )
{
KdPrint(("ZwSetValueKey Successfully ! \r\n"));
}
else
{
KdPrint(("ZwSetValueKey Unsuccessfully ! \r\n"));
}
// 查询注册表项
nStatus = ZwQueryValueKey(hRegistry,
&uniValueName,
KeyValuePartialInformation,
NULL,
NULL,
&ulResult);
// STATUS_BUFFER_TOO_SMALL表示缓冲区太小
if ( nStatus == STATUS_BUFFER_TOO_SMALL || ulResult != 0 )
{
pKeyValuePartialClass = ExAllocatePool(PagedPool, ulResult);
nStatus = ZwQueryValueKey(hRegistry,
&uniValueName,
KeyValuePartialInformation,
pKeyValuePartialClass,
ulResult,
&ulResult);
KdPrint(("%S \r\n", pKeyValuePartialClass->Data));
ExFreePool(pKeyValuePartialClass);
}
else
{
KdPrint(("ZwQueryValueKey Unsuccessfully ! \r\n"));
}
}
VOID OpenKey()
{
UNICODE_STRING uniRegPath;
OBJECT_ATTRIBUTES objAttributes;
HANDLE hRegistry;
NTSTATUS nStatus;
RtlInitUnicodeString(&uniRegPath, REG_PATH);
InitializeObjectAttributes(&objAttributes,
&uniRegPath,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
// 打开注册表
nStatus = ZwOpenKey(&hRegistry, KEY_ALL_ACCESS, &objAttributes);
if ( NT_SUCCESS(nStatus) )
{
KdPrint(("ZwOpenKey Successfully ! \r\n"));
}
else
{
KdPrint(("ZwOpenKey Unsuccessfully ! \r\n"));
return ;
}
// 查询并设置
QueryAndSetKey(hRegistry);
// 关闭注册表句柄
ZwClose(hRegistry);
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
pDriverObject->DriverUnload = DriverUnload;
CreateKey();
OpenKey();
return STATUS_SUCCESS;
}