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

相关参考:

内核字符串

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

  • UNICODE_STRING

    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

    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* 来保存和操作字符串。

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

字符串和数字之间的转换

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

字符串拷贝和比较

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

  • RtlInitEmptyUnicodeString
  • RtlCopyUnicodeString
  • RtlAppendUnicodeToString
  • RtlEqualUnicodeString

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

大小写操作

相关API:

  • RtlUpcaseUnicodeString
  • RtlUpcaseUnicodeChar