【驱动】开发相关API整理

概述:驱动开发过程中,相关API的学习整理

[toc]

链表

定义链表结构体

1
2
3
4
5
6
7
typedef struct
{
DWORD Pid;
UCHAR ProcessName[2048];
DWORD Handle;
LIST_ENTRY ListEntry;
} ProcessList;

初始化 InitializeListHead

1
2
3
4
LIST_ENTRY linkListHead;

// 初始化链表头部
InitializeListHead(&linkListHead);

插入链表 InsertTailList

1
2
3
pData = (ProcessList*)ExAllocatePool(PagedPool, sizeof(ProcessList));

InsertTailList(&linkListHead, &pData->ListEntry);

判断是否为空 IsListEmpty

1
2
3
4
if(!IsListEmpty(&linkListHead))
{
DbgPrint("Empty");
}

移除链表 RemoveHeadList

1
LIST_ENTRY* pEntry = RemoveHeadList(&linkListHead);

获取链表当中的结构

1
2
pData = CONTAINING_RECORD(pEntry, ProcessList, ListEntry);
DbgPrint("Pid[%d], ProcessName[%s], Handle[0x%x] \n", pData->Pid, pData->ProcessName, pData->Handle);

安装卸载相关

WdfDriverCreate

WdfDriverCreate 是 Windows Driver Frameworks (WDF) 中用于创建驱动程序的函数。它用于初始化驱动程序,并将其与 Windows 操作系统进行集成。

WdfDriverCreate 函数需要填写一个 WDFDRIVER 结构体来定义驱动程序的属性和行为。以下是一些常见的参数:

  • DriverName: 驱动程序的名称,用于在调试和日志记录中标识驱动程序。
  • DriverVersion: 驱动程序的版本号,用于标识驱动程序的版本。
  • DeviceAddVersion: 用于添加新设备的 WDF 函数版本。
  • DriverUnload: 驱动程序卸载时的回调函数,用于清理资源并执行其他必要的操作。
  • DispatchTable: 一个包含驱动程序处理 IRP 请求的回调函数的表。
  • RegistryPath: 驱动程序在注册表中的路径,用于存储配置信息和设置。

WdfDeviceCreate

WdfDeviceCreate 是 Windows Driver Frameworks (WDF) 中的一个函数,用于创建设备对象并初始化与设备相关的数据结构。它是 WDF 中非常重要和常用的函数之一。

WdfDeviceCreate 函数需要填写一个 WDFDEVICE_INIT 结构体来定义设备的属性和行为。以下是一些常见的参数:

  • Driver: 指向驱动程序的指针,用于关联设备和驱动程序。
  • DeviceAttributes: 指向调用方分配的 WDF_OBJECT_ATTRIBUTES 结构的指针,该结构包含新设备的属性。
  • StackSize: 设备的堆栈大小,用于定义设备对象的执行环境。
  • DeviceInit: 指向 WDFDEVICE_INIT 结构的指针的地址,用于初始化设备的状态和行为。

在调用 WdfDeviceCreate 之后,WDF 将使用提供的参数来创建并初始化一个设备对象。然后,设备可以使用 WDF 提供的其他函数来管理设备的状态、处理请求以及与其他设备或驱动程序进行交互。

需要注意的是,WdfDeviceCreate 是一个高级别的函数,通常用于创建完整的设备对象。对于较简单的设备或需要更精细控制的情况,可能需要使用其他 WDF 函数来手动创建和配置设备对象。同时,在使用 WdfDeviceCreate 和其他 WDF 函数时,应注意遵循正确的使用方法和规范,以避免产生潜在的问题和风险。

实用函数

绕过签名检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 绕过签名检查
BOOLEAN BypassCheckSign(PDRIVER_OBJECT pDriverObject)
{
#ifdef _WIN64
typedef struct _KLDR_DATA_TABLE_ENTRY
{
LIST_ENTRY listEntry;
ULONG64 __Undefined1;
ULONG64 __Undefined2;
ULONG64 __Undefined3;
ULONG64 NonPagedDebugInfo;
ULONG64 DllBase;
ULONG64 EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING path;
UNICODE_STRING name;
ULONG Flags;
USHORT LoadCount;
USHORT __Undefined5;
ULONG64 __Undefined6;
ULONG CheckSum;
ULONG __padding1;
ULONG TimeDateStamp;
ULONG __padding2;
} KLDR_DATA_TABLE_ENTRY, * PKLDR_DATA_TABLE_ENTRY;
#else
typedef struct _KLDR_DATA_TABLE_ENTRY
{
LIST_ENTRY listEntry;
ULONG unknown1;
ULONG unknown2;
ULONG unknown3;
ULONG unknown4;
ULONG unknown5;
ULONG unknown6;
ULONG unknown7;
UNICODE_STRING path;
UNICODE_STRING name;
ULONG Flags;
} KLDR_DATA_TABLE_ENTRY, * PKLDR_DATA_TABLE_ENTRY;
#endif

PKLDR_DATA_TABLE_ENTRY pLdrData = (PKLDR_DATA_TABLE_ENTRY)pDriverObject->DriverSection;
pLdrData->Flags = pLdrData->Flags | 0x20;

return TRUE;
}

如何使用

1
2
3
4
5
6
7
8
9
10
11
// 驱动入口地址
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
if (BypassCheckSign(Driver))
DbgPrint("Bypass Sign Success.");

DbgPrint("Driver loaded. \n");

Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

内存相关

申请内存 ExAllocatePool

申请内存使用 ExAllocatePoolExAllocatePoolWithTagExAllocatePool2 接口

原型

1
2
3
4
PVOID ExAllocatePool(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes
);
  • PoolType:指定内存池的类型,可以是 NonPagedPool(非分页内存池)或 PagedPool(分页内存池)等。
  • NumberOfBytes:指定要分配的内存大小,最好是4的倍数。
  • 返回值:返回分配的内存地址,一定是内核模式地址。如果返回NULL,则代表分配失败。

ExAllocatePool 与 ExAllocatePool2/ExAllocatePool3

从 Windows 10 版本 2004 开始,推荐使用 ExAllocatePool2ExAllocatePool3 来替代 ExAllocatePool。新的 API 默认会将分配的内存初始化为零,有助于避免内存泄漏相关的bug。

示例代码

1
2
3
4
5
6
PVOID Allocation = ExAllocatePool(PagedPool, 100);
if (Allocation == NULL) {
// 处理内存分配失败的情况
} else {
// 使用分配的内存
}

在新的驱动中,应该使用 ExAllocatePool2 接口

1
2
3
4
5
6
PVOID Allocation = ExAllocatePool2(POOL_FLAG_PAGED, 100, 'abcd');
if (Allocation == NULL) {
// 处理内存分配失败的情况
} else {
// 使用分配的内存
}

填充内存 RtlZeroMemory

1
2
3
// 分配内核堆空间
pData = (ProcessList*)ExAllocatePool(PagedPool, sizeof(ProcessList));
RtlZeroMemory(pData, sizeof(ProcessList));

拷贝内存 RtlCopyMemory

1
2
3
4
// 设置变量
pData->Pid = (DWORD)PsGetProcessId(eproc);
RtlCopyMemory(pData->ProcessName, PsGetProcessImageFileName(eproc), strlen(PsGetProcessImageFileName(eproc)));
pData->Handle = (DWORD)PsGetProcessInheritedFromUniqueProcessId(eproc);

释放内存 ExFreePool

1
ExFreePool(pData);

进程相关

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

// 函数返回给定进程的进程环境块(Process Environment Block,PEB)的指针
extern PVOID PsGetProcessPeb(_In_ PEPROCESS Process);

// 此函数通过给定的进程 ID 查找对应的进程,并将进程的 EPROCESS 结构指针存储在提供的指针变量中。
NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS* Process);

// 此函数返回给定进程的 Wow64 进程环境的指针。Wow64 进程环境是用于在 64 位 Windows 上运行 32 位应用程序的兼容性层。
extern NTKERNELAPI PVOID PsGetProcessWow64Process(_In_ PEPROCESS Process);

// 此函数返回给定进程的映像文件名,即执行该进程的可执行文件的名称。
extern NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process);

// 此函数返回给定进程所继承的唯一进程 ID,即父进程ID。
extern NTKERNELAPI HANDLE PsGetProcessInheritedFromUniqueProcessId(IN PEPROCESS Process);

【驱动】开发相关API整理
https://hodlyounger.github.io/2023/10/27/A_OS/Windows/驱动/windows驱动开发教程/【驱动】开发相关API整理/
作者
mingming
发布于
2023年10月27日
许可协议