概述:内核开发字符串使用
相关参考:
- 驱动开发:内核字符串转换方法 | LyShark®
内核字符串
内核编程中主要使用的字符串有两种格式:
-
UNICODE_STRINGtypedef 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_STRINGtypedef 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 可以用于字符串初始化操作,常见的主要是:
RtlInitUnicodeStringRtlInitAnsiString
字符串转换
针对 ANSI 和 UNICODE 两种编码格式之间进行转换操作,主要的 API 是:
RtlUnicodeStringToAnsiStringRtlAnsiStringToUnicodeString
另外的就是可以使用 CHAR*、WCHAR* 来保存和操作字符串。
Demo1
需要注意的是,参考的教程直接修改内存的方式会崩溃蓝屏。修改常量区内存需要先修改内存属性才可以。这里我是在网上随便找了一个方式。
// #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;
}
输出:
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
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
字符串和数字之间的转换
Demo2
// #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;
}输出:
Hello World, Driver
PDRIVER_OBJECT-> [0x0b68a570]
PUNICODE_STRING->[\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\KmdfString2]
字符串 -> 数字:100
数字 -> 字符串:100
Bye, Driver
PDRIVER_OBJECT-> [0x0b68a570]
PUNICODE_STRING->[\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\KmdfString2]
字符串 -> 数字:100
数字 -> 字符串:100
Bye, Driver
字符串拷贝和比较
UNICODE_STRING 和 ANSI_STRING 都是结构体,因此在使用上可以直接通过申请内存的方式来指定字符串的大小。
RtlInitEmptyUnicodeStringRtlCopyUnicodeStringRtlAppendUnicodeToStringRtlEqualUnicodeString
Demo3
// #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;
}输出:
Hello World, Driver
PDRIVER_OBJECT-> [0x0fb6fa80]
PUNICODE_STRING->[\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\KmdfStringCopy]
[*] 输出字符串:Hello World! Kernel Test
[*] 字符串相等
Bye, Driver
PDRIVER_OBJECT-> [0x0fb6fa80]
PUNICODE_STRING->[\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\KmdfStringCopy]
[*] 输出字符串:Hello World! Kernel Test
[*] 字符串相等
Bye, Driver
大小写操作
相关API:
RtlUpcaseUnicodeStringRtlUpcaseUnicodeChar