【驱动学习】4.1-内存操作及内存属性

文章目录
  1. 1. 内存属性
  2. 2. 内存操作
    1. 2.1. ExAllocatePool 内存申请
    2. 2.2. RtlZeroMemory 内存初始化
    3. 2.3. RtlCopyMemory 内存拷贝
    4. 2.4. ExFreePool 内存释放
  3. 3. Demo
  4. 4. 双向链表
    1. 4.1. Demo

概述:

内存属性

在了解自旋锁之前需要先了解下内核的内存分配相关内容,相关的 API 可以参考 内存相关 一文。 这里补充下 ExAllocatePool 的参数1,也就是申请的页属性参数 POOL_TYPE

微软官方说明:_POOL_TYPE (wdm.h) - Windows drivers | Microsoft Learn

这里主要就常用的三个进行简单说明:

  • NonPagedPool 非分页池,这是不可分页的系统内存。 可以从任何 IRQL 访问非分页池,但它是一种稀缺资源,驱动程序应仅在必要时进行分配。该内存不会被交换到磁盘上,并且可以直接被内核访问,适用于需要快速访问的内存,例如驱动程序的代码、中断处理程序、系统调用等。

  • PagePool 分页池,它是可分页的系统内存。 只能在 IRQL < DISPATCH_LEVEL分配和访问分页池。

  • NonPagedPoolExecute

    带有执行权限的非分页内存。如果是使用 NonPagedPoolExecute 申请的内存则需要留意,避免被利用。 从 Windows 8 开始,NonPagedPoolExecuteNonPagedPool 值的备用名称。 此值指示分配的内存是非分页且可执行的,即在此内存中启用指令执行。 若要从早期版本的 Windows 移植驱动程序,通常应将驱动程序源代码中 NonPagedPool 名称的所有或大多数实例替换为 NonPagedPoolNx。 避免将 NonPagedPool 名称的实例替换为 NonPagedPoolExecute ,除非显式需要可执行内存。 有关详细信息,请参阅 No-Execute (NX) Nonpaged Pool。

内存操作

ExAllocatePool 内存申请

RtlZeroMemory 内存初始化

RtlCopyMemory 内存拷贝

ExFreePool 内存释放

Demo

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// #include <ntddk.h>

#include <ntifs.h>

typedef struct _MyStruct
{
ULONG x;
ULONG y;
}MyStruct, *PMyStruct;

NTSTATUS UnloadDriver(PDRIVER_OBJECT pDriver)
{
DbgPrint("Bye, Driver\n");

return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
DbgPrint("Hello World, Driver\n");
DbgPrint("PDRIVER_OBJECT-> [0x%08x]\n", pDriver);
DbgPrint("PUNICODE_STRING->[%ws]\n", pReg->Buffer);

// 1 用于分配与释放非标签内存
PVOID buffer = ExAllocatePool(NonPagedPool, 1024);

DbgPrint("[*] 分配内存地址=%p \n", buffer);

if (buffer)
{
ExFreePool(buffer);
}

// 用于分配带有标签的内存
PMyStruct pStu = (PMyStruct)ExAllocatePoolWithTag(NonPagedPoolExecute, sizeof(PMyStruct), "KernelTest");

if (pStu)
{
pStu->x = 100;
pStu->y = 200;

DbgPrint("[*] 分配内存 x = %d, y= %d", pStu->x, pStu->y);

ExFreePoolWithTag(pStu, "KernelTest");
}

UNICODE_STRING dst = { 0 };
UNICODE_STRING src = RTL_CONSTANT_STRING(L"Hello KernelTest");

dst.Buffer = (PWCHAR)ExAllocatePool(NonPagedPool, src.Length);
if (dst.Buffer == NULL)
{
DbgPrint("[-] 分配空间错误");
}

dst.Length = dst.MaximumLength = src.Length;
RtlCopyUnicodeString(&dst, &src);

DbgPrint("[*] 输出拷贝 = %wZ", dst);

pDriver->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
}

输出

1
2
3
4
5
6
Hello World, Driver
PDRIVER_OBJECT-> [0xb0402e30]
PUNICODE_STRING->[\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\KmdfMemory]
[*] 分配内存地址=FFFFAA02B11358F0
[*] 分配内存 x = 100, y= 200
[*] 输出拷贝 = Hello KernelTest

双向链表

双向链表的结构体如下所示:

1
2
3
4
5
typedef struct _LIST_ENTRY
{
struct _LIST_ENTRY* Flink; // 指向后一个节点
struct _LIST_ENTRY* Blink; // 指向前一个节点
}LIST_ENTRY, * PLIST_ENTRY;

需要注意的是:FLink 指向后一个节点,Blink 指向前一个结点。

多线程访问

需要注意的是,多线程访问同一数据结构时会存在线程同步问题,常见的使用方式就是使用锁机制进程同步。自旋锁是一种常用的锁机制,它是一种高 IRQL 锁,用于同步和独占地访问某个资源。

**自旋锁的基本思想:**当一个线程尝试获取锁时,如果锁已经被占用,则该线程不断循环(即自旋),直到锁被释放。自旋锁适用于锁的持有时间较短,且竞争者较少的情况下,可以避免进程上下文的切换和调度开销。在下一章将详细记录和说明。

Demo

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// #include <ntddk.h>

#include <ntifs.h>

typedef struct _MyStruct
{
ULONG x;
ULONG y;
LIST_ENTRY lpListEntry;
}MyStruct, * PMyStruct;

NTSTATUS UnloadDriver(PDRIVER_OBJECT pDriver)
{
DbgPrint("Bye, Driver\n");

return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
DbgPrint("Hello World, Driver\n");
DbgPrint("PDRIVER_OBJECT-> [0x%016x]\n", pDriver);
DbgPrint("PUNICODE_STRING->[%ws]\n", pReg->Buffer);

// 初始化头节点
LIST_ENTRY ListHeader = { 0 };
InitializeListHead(&ListHeader);

// 定义链表元素
MyStruct testA = { 0 };
MyStruct testB = { 0 };
MyStruct testC = { 0 };

testA.x = 10;
testA.y = 20;

testB.x = 100;
testB.y = 200;

testC.x = 1000;
testC.y = 2000;

// 分别插入节点到头部和尾部
InsertHeadList(&ListHeader, &testA.lpListEntry);
InsertTailList(&ListHeader, &testB.lpListEntry);
InsertTailList(&ListHeader, &testC.lpListEntry);

// 输出链表数据
PLIST_ENTRY pListEntry = NULL;
pListEntry = ListHeader.Flink;

int i = 0;

while (pListEntry != &ListHeader)
{
// 计算出成员距离结构体顶部内存距离
PMyStruct ptr = CONTAINING_RECORD(pListEntry, MyStruct, lpListEntry);
DbgPrint("节点元素x = %d, 节点元素y = %d\n", ptr->x, ptr->y);

if (i++ > 10)
{
break;
}
// 获取下一个链表地址
pListEntry = pListEntry->Flink;
}

pDriver->DriverUnload = UnloadDriver;

return STATUS_SUCCESS;
}

驱动输出:

1
2
3
4
5
6
Hello World, Driver
PDRIVER_OBJECT-> [0x00000000a78b4950]
PUNICODE_STRING->[\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\KmdfListEntry]
节点元素x = 10, 节点元素y = 20
节点元素x = 100, 节点元素y = 200
节点元素x = 1000, 节点元素y = 2000