【驱动学习】6-内核字符串

概述:内核开发字符串使用

相关参考:

内核字符串

内核编程中主要使用的字符串有两种格式:

  • UNICODE_STRING

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    #ifdef MIDL_PASS
    [size_is(MaximumLength / 2), length_is((Length) / 2) ] USHORT * Buffer;
    #else // MIDL_PASS
    _Field_size_bytes_part_opt_(MaximumLength, Length) PWCH Buffer;
    #endif // MIDL_PASS
    } UNICODE_STRING;
    typedef UNICODE_STRING *PUNICODE_STRING;
    typedef const UNICODE_STRING *PCUNICODE_STRING;
  • ANSI_STRING

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    typedef struct _STRING {
    USHORT Length;
    USHORT MaximumLength;
    #ifdef MIDL_PASS
    [size_is(MaximumLength), length_is(Length) ]
    #endif // MIDL_PASS
    _Field_size_bytes_part_opt_(MaximumLength, Length) PCHAR Buffer;
    } STRING;
    typedef STRING *PSTRING;
    typedef STRING ANSI_STRING;

在 Windows 内核中,字符串的处理十分重要。稍有不慎就会造成系统蓝屏。因此,对于字符串的处理必须遵循严格的安全规则,以确保不会引发各种安全漏洞。

内核字符串初始化

内核开发模式下提供了多个 API 可以用于字符串初始化操作,常见的主要是:

  • RtlInitUnicodeString
  • RtlInitAnsiString

字符串转换

针对 ANSI 和 UNICODE 两种编码格式之间进行转换操作,主要的 API 是:

  • RtlUnicodeStringToAnsiString
  • RtlAnsiStringToUnicodeString

另外的就是可以使用 CHAR*WCHAR* 来保存和操作字符串。

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// #include <ntddk.h>

#include <ntifs.h>

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

return STATUS_SUCCESS;
}

KIRQL WPOFFx64()
{
KIRQL irql = KeRaiseIrqlToDpcLevel();
UINT64 cr0 = __readcr0();
cr0 &= 0xfffffffffffeffff;
__writecr0(cr0);
_disable();
return irql;
}

void WPONx64(KIRQL irql)
{
UINT64 cr0 = __readcr0();
cr0 |= 0x10000;
_enable();
__writecr0(cr0);
KeLowerIrql(irql);
}

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);

// 定义内核字符串
ANSI_STRING ansi;
ANSI_STRING ansiDst = {0}; // 最好赋个初值
UNICODE_STRING unicode;
UNICODE_STRING str;

// 定义普通字符串
char* chr = "Hello Kernel Test";
wchar_t* wchr = (WCHAR*)L"Hello Kernel Test";

// 初始化字符串,有多种方式可以选择
RtlInitAnsiString(&ansi, chr);
RtlInitUnicodeString(&unicode, wchr);
RtlInitUnicodeString(&str, L"Hello Kernel Test");

#if 0
// 创建 MDL
PMDL mdl = IoAllocateMdl(chr, strlen(chr) + 1, FALSE, FALSE, NULL);
if (!mdl) {
DbgPrint("Failed to allocate MDL.\n");
return STATUS_INSUFFICIENT_RESOURCES;
}

// 锁定内存页
MmBuildMdlForNonPagedPool(mdl);

// 映射 MDL 到系统地址空间
PVOID baseAddress = MmGetSystemAddressForMdlSafe(mdl, MdlMappingNoExecute);
if (!baseAddress) {
DbgPrint("Failed to map MDL to system address space.\n");
IoFreeMdl(mdl);
return STATUS_INSUFFICIENT_RESOURCES;
}

// 修改内存保护属性为可读写
NTSTATUS status = MmProtectMdlSystemAddress(mdl, PAGE_READWRITE);
if (!NT_SUCCESS(status)) {
DbgPrint("Failed to change memory protection: %x\n", status);
IoFreeMdl(mdl);
return status;
}
#endif

// 改变原始字符串,看下效果,这里应该是不能修改的,编译后位于常量区,要修改必须先修改内存属性才可以。否则会报错
KIRQL irql = WPOFFx64();
// 修改字符串内容
chr[0] = 'h'; // 将 'H' 改为 'h'
chr[6] = 'k'; // 将 'K' 改为 'k'

WPONx64(irql);


// 打印
DbgPrint("[*] 输出ANSI: %Z \n", &ansi);
DbgPrint("[*] 输出WCHAR: %Z \n", &unicode);
DbgPrint("[*] 输出字符串: %wZ \n", &str);

NTSTATUS flag = RtlUnicodeStringToAnsiString(&ansiDst, &unicode, TRUE);
if (NT_SUCCESS(flag))
{
DbgPrint("[*] RtlUnicodeStringToAnsiString Called Print: %Z", &ansiDst);
}

pDriver->DriverUnload = UnloadDriver;

return STATUS_SUCCESS;
}

输出:

1
2
3
4
5
6
7
8
Hello World, Driver
PDRIVER_OBJECT-> [0x0edcee30]
PUNICODE_STRING->[\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\KmdfString]
[*] 输出ANSI: hello kernel Test
[*] 输出WCHAR: H
[*] 输出字符串: Hello Kernel Test
[*] RtlUnicodeStringToAnsiString Called Print: Hello Kernel Test
Bye, Driver

字符串和数字之间的转换

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
// #include <ntddk.h>

#include <ntifs.h>

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);


UNICODE_STRING unicode_buffer_source = { 0 };
UNICODE_STRING unicode_buffer_target = { 0 };

// 字符串转为数字
ULONG number;
RtlInitUnicodeString(&unicode_buffer_source, L"100");
NTSTATUS flag = RtlUnicodeStringToInteger(&unicode_buffer_source, 10, &number);

if (NT_SUCCESS(flag))
{
DbgPrint("[*] 字符串 -> 数字:%d \n", number);
}

// 数字转字符串
unicode_buffer_target.Buffer = (PWSTR)ExAllocatePool(PagedPool, 1024);
unicode_buffer_target.MaximumLength = 1024;

flag = RtlIntegerToUnicodeString(number, 10, &unicode_buffer_target);
if (NT_SUCCESS(flag))
{
DbgPrint("[*] 数字 -> 字符串:%wZ \n", unicode_buffer_target);
}


{
RtlFreeUnicodeString(&unicode_buffer_target);
}

pDriver->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
}

输出:

1
2
3
4
5
6
Hello World, Driver
PDRIVER_OBJECT-> [0x0b68a570]
PUNICODE_STRING->[\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\KmdfString2]
字符串 -> 数字:100
数字 -> 字符串:100
Bye, Driver

字符串拷贝和比较

UNICODE_STRINGANSI_STRING 都是结构体,因此在使用上可以直接通过申请内存的方式来指定字符串的大小。

  • RtlInitEmptyUnicodeString
  • RtlCopyUnicodeString
  • RtlAppendUnicodeToString
  • RtlEqualUnicodeString

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// #include <ntddk.h>

#include <ntifs.h>

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

return STATUS_SUCCESS;
}

void StringCopyDemo()
{
UNICODE_STRING dst;
WCHAR dst_buf[256] = { 0 };
NTSTATUS status;

// 初始化字符串
UNICODE_STRING src = RTL_CONSTANT_STRING(L"Hello World!");

// 字符串初始化为空,长度为256
RtlInitEmptyUnicodeString(&dst, dst_buf, 256 * sizeof(WCHAR));

if (&dst)
{
RtlCopyUnicodeString(&dst, &src);
}

// 在 dst 之后追加
status = RtlAppendUnicodeToString(&dst, L"KernelTest");
if (status == STATUS_SUCCESS)
{
DbgPrint("[*] 输出操作后的字符串:%wZ", &dst);
}
}

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);

UNICODE_STRING unicode_buffer = { 0 };
UNICODE_STRING unicode_buffer_dst = { 0 };
wchar_t* wchar_string = L"Hello World! Kernel Test";

// 初始化并分配
RtlInitUnicodeString(&unicode_buffer_dst, L"Hello World! Kernel Test");

// 设置最大长度
unicode_buffer.MaximumLength = 1024;

// 分配内存空间
unicode_buffer.Buffer = (PWSTR)ExAllocatePool(PagedPool, 1024);

// 设置字符长度,由于是宽字符,所以要*2
unicode_buffer.Length = wcslen(wchar_string) * 2;

// 保证缓冲区足够大,否则程序终止
ASSERT(unicode_buffer.MaximumLength >= unicode_buffer.Length);

// 将 wchar_string 中的字符串拷贝到 uncode_buffer.Buffer
RtlCopyMemory(unicode_buffer.Buffer, wchar_string, unicode_buffer.Length);

// 设置字符串长度,并打印
unicode_buffer.Length = wcslen(wchar_string) * 2;
DbgPrint("[*] 输出字符串:%wZ \n", unicode_buffer);

// 比较字符串是否相等
if (RtlEqualUnicodeString(&unicode_buffer, &unicode_buffer_dst, TRUE))
{
DbgPrint("[*] 字符串相等");
}
else
{
DbgPrint("[*] 字符串不相等");
}

// 释放堆空间
ExFreePool(unicode_buffer.Buffer);

unicode_buffer.Buffer = NULL;
unicode_buffer.MaximumLength = 0;
unicode_buffer.Length = 0;

pDriver->DriverUnload = UnloadDriver;

return STATUS_SUCCESS;
}

输出:

1
2
3
4
5
6
Hello World, Driver
PDRIVER_OBJECT-> [0x0fb6fa80]
PUNICODE_STRING->[\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\KmdfStringCopy]
[*] 输出字符串:Hello World! Kernel Test
[*] 字符串相等
Bye, Driver

大小写操作

相关API:

  • RtlUpcaseUnicodeString
  • RtlUpcaseUnicodeChar

【驱动学习】6-内核字符串
https://hodlyounger.github.io/2025/01/24/A_OS/Windows/驱动/windows驱动开发教程/【驱动学习】6-内核字符串/
作者
mingming
发布于
2025年1月24日
许可协议