1、课程目标
- 掌握内核字符串类型和结构
- 学会使用RtlXxx字符串函数
- 理解ANSI和Unicode字符串转换
- 掌握安全的字符串处理方法
2、名词解释
| 术语 |
解释 |
| UNICODE_STRING |
Unicode字符串结构 |
| ANSI_STRING |
ANSI字符串结构 |
| PCWSTR |
指向宽字符常量的指针 |
| Rtl函数 |
Runtime Library运行时库函数 |
| Ntstrsafe |
安全字符串函数库 |
3、使用工具
| 工具 |
用途 |
| WinDbg |
查看字符串内容 |
| IDA Pro |
分析字符串操作 |
4、技术原理
4.1、字符串结构
// UNICODE_STRING结构
typedef struct _UNICODE_STRING {
USHORT Length; // 字符串字节长度(不含结尾NULL)
USHORT MaximumLength; // 缓冲区最大字节长度
PWSTR Buffer; // 字符串缓冲区指针
} UNICODE_STRING, *PUNICODE_STRING;
// ANSI_STRING结构
typedef struct _ANSI_STRING {
USHORT Length; // 字符串字节长度
USHORT MaximumLength; // 缓冲区最大长度
PSTR Buffer; // 字符串缓冲区
} ANSI_STRING, *PANSI_STRING;
5、代码实现
5.1、示例1:基础字符串操作
// StringBasics.c - 基础字符串操作
#include <ntddk.h>
#include <ntstrsafe.h>
VOID DemonstrateStringBasics() {
UNICODE_STRING uStr1, uStr2, uStr3;
WCHAR buffer[256];
// ==================== 初始化字符串 ====================
// 方法1:从常量初始化(只读)
RtlInitUnicodeString(&uStr1, L"Hello, Kernel!");
DbgPrint("[String] uStr1: %wZ\n", &uStr1);
DbgPrint("[String] Length: %d, MaxLength: %d\n",
uStr1.Length, uStr1.MaximumLength);
// 方法2:使用自定义缓冲区
uStr2.Buffer = buffer;
uStr2.Length = 0;
uStr2.MaximumLength = sizeof(buffer);
// 方法3:使用RTL_CONSTANT_STRING宏(编译时初始化)
DECLARE_CONST_UNICODE_STRING(constStr, L"Constant String");
DbgPrint("[String] constStr: %wZ\n", &constStr);
// ==================== 字符串复制 ====================
// 复制字符串
RtlCopyUnicodeString(&uStr2, &uStr1);
DbgPrint("[String] Copied: %wZ\n", &uStr2);
// ==================== 字符串追加 ====================
UNICODE_STRING appendStr;
RtlInitUnicodeString(&appendStr, L" World!");
RtlAppendUnicodeStringToString(&uStr2, &appendStr);
DbgPrint("[String] Appended: %wZ\n", &uStr2);
// ==================== 字符串比较 ====================
UNICODE_STRING cmpStr;
RtlInitUnicodeString(&cmpStr, L"Hello, Kernel!");
// 区分大小写比较
if (RtlCompareUnicodeString(&uStr1, &cmpStr, FALSE) == 0) {
DbgPrint("[String] Strings are equal (case-sensitive)\n");
}
// 不区分大小写比较
RtlInitUnicodeString(&cmpStr, L"HELLO, KERNEL!");
if (RtlCompareUnicodeString(&uStr1, &cmpStr, TRUE) == 0) {
DbgPrint("[String] Strings are equal (case-insensitive)\n");
}
// ==================== 字符串相等判断 ====================
if (RtlEqualUnicodeString(&uStr1, &cmpStr, TRUE)) {
DbgPrint("[String] Strings are equal\n");
}
}
5.2、示例2:动态字符串分配
// DynamicString.c - 动态字符串操作
#include <ntddk.h>
// 分配字符串缓冲区
NTSTATUS AllocateUnicodeString(PUNICODE_STRING String, USHORT maxLength) {
String->Buffer = (PWSTR)ExAllocatePoolWithTag(
PagedPool, maxLength, 'rtSU');
if (!String->Buffer) {
return STATUS_INSUFFICIENT_RESOURCES;
}
String->Length = 0;
String->MaximumLength = maxLength;
RtlZeroMemory(String->Buffer, maxLength);
return STATUS_SUCCESS;
}
// 释放字符串缓冲区
VOID FreeUnicodeString(PUNICODE_STRING String) {
if (String->Buffer) {
ExFreePoolWithTag(String->Buffer, 'rtSU');
String->Buffer = NULL;
String->Length = 0;
String->MaximumLength = 0;
}
}
// 复制字符串(自动分配)
NTSTATUS DuplicateUnicodeString(
PUNICODE_STRING Dest,
PCUNICODE_STRING Source
) {
NTSTATUS status;
status = AllocateUnicodeString(Dest, Source->Length + sizeof(WCHAR));
if (!NT_SUCCESS(status)) {
return status;
}
RtlCopyUnicodeString(Dest, Source);
return STATUS_SUCCESS;
}
// 格式化字符串
NTSTATUS FormatString(PUNICODE_STRING Dest, PCWSTR Format, ...) {
NTSTATUS status;
va_list args;
va_start(args, Format);
status = RtlUnicodeStringVPrintf(Dest, Format, args);
va_end(args);
return status;
}
// 测试动态字符串
VOID TestDynamicString() {
UNICODE_STRING dynStr = {0};
UNICODE_STRING srcStr;
NTSTATUS status;
// 分配512字节缓冲区
status = AllocateUnicodeString(&dynStr, 512);
if (NT_SUCCESS(status)) {
// 格式化写入
RtlUnicodeStringPrintf(&dynStr, L"PID: %d, Name: %ws",
1234, L"notepad.exe");
DbgPrint("[Dynamic] %wZ\n", &dynStr);
FreeUnicodeString(&dynStr);
}
// 复制字符串
RtlInitUnicodeString(&srcStr, L"Source String");
status = DuplicateUnicodeString(&dynStr, &srcStr);
if (NT_SUCCESS(status)) {
DbgPrint("[Duplicated] %wZ\n", &dynStr);
FreeUnicodeString(&dynStr);
}
}
5.3、示例3:ANSI与Unicode转换
// StringConversion.c - 字符串转换
#include <ntddk.h>
// ANSI转Unicode
NTSTATUS AnsiToUnicode(PANSI_STRING AnsiStr, PUNICODE_STRING UnicodeStr) {
return RtlAnsiStringToUnicodeString(UnicodeStr, AnsiStr, TRUE);
// TRUE表示自动分配缓冲区,需要用RtlFreeUnicodeString释放
}
// Unicode转ANSI
NTSTATUS UnicodeToAnsi(PUNICODE_STRING UnicodeStr, PANSI_STRING AnsiStr) {
return RtlUnicodeStringToAnsiString(AnsiStr, UnicodeStr, TRUE);
// 需要用RtlFreeAnsiString释放
}
// C字符串与UNICODE_STRING转换
VOID StringConversionDemo() {
// ==================== 从C字符串创建 ====================
UNICODE_STRING uStr;
const char* ansiCStr = "Hello ANSI";
const WCHAR* wideCStr = L"Hello Wide";
// 从宽字符C字符串
RtlInitUnicodeString(&uStr, wideCStr);
// 从ANSI C字符串(需要转换)
ANSI_STRING aStr;
UNICODE_STRING convertedStr;
RtlInitAnsiString(&aStr, ansiCStr);
if (NT_SUCCESS(RtlAnsiStringToUnicodeString(&convertedStr, &aStr, TRUE))) {
DbgPrint("[Convert] ANSI to Unicode: %wZ\n", &convertedStr);
RtlFreeUnicodeString(&convertedStr);
}
// ==================== Unicode转ANSI ====================
ANSI_STRING convertedAnsi;
RtlInitUnicodeString(&uStr, L"Unicode String");
if (NT_SUCCESS(RtlUnicodeStringToAnsiString(&convertedAnsi, &uStr, TRUE))) {
DbgPrint("[Convert] Unicode to ANSI: %Z\n", &convertedAnsi);
RtlFreeAnsiString(&convertedAnsi);
}
// ==================== 提取C字符串 ====================
// UNICODE_STRING不一定以NULL结尾,安全提取方法
WCHAR cStrBuffer[256];
RtlInitUnicodeString(&uStr, L"Extract this");
if (uStr.Length < sizeof(cStrBuffer) - sizeof(WCHAR)) {
RtlCopyMemory(cStrBuffer, uStr.Buffer, uStr.Length);
cStrBuffer[uStr.Length / sizeof(WCHAR)] = L'\0';
DbgPrint("[Extract] C string: %ws\n", cStrBuffer);
}
}
5.4、示例4:安全字符串函数
// SafeString.c - 安全字符串操作
#include <ntddk.h>
#include <ntstrsafe.h>
VOID SafeStringDemo() {
WCHAR buffer[256];
NTSTATUS status;
size_t remaining;
// ==================== RtlStringCbCopyW ====================
// 按字节复制
status = RtlStringCbCopyW(buffer, sizeof(buffer), L"Hello");
if (NT_SUCCESS(status)) {
DbgPrint("[Safe] Copy: %ws\n", buffer);
}
// ==================== RtlStringCchCopyW ====================
// 按字符复制
status = RtlStringCchCopyW(buffer, 256, L"World");
if (NT_SUCCESS(status)) {
DbgPrint("[Safe] Copy: %ws\n", buffer);
}
// ==================== RtlStringCbCatW ====================
// 连接字符串
RtlStringCbCopyW(buffer, sizeof(buffer), L"Hello ");
status = RtlStringCbCatW(buffer, sizeof(buffer), L"World!");
if (NT_SUCCESS(status)) {
DbgPrint("[Safe] Concat: %ws\n", buffer);
}
// ==================== RtlStringCbPrintfW ====================
// 格式化
status = RtlStringCbPrintfW(buffer, sizeof(buffer),
L"PID=%d, Name=%ws",
1234, L"test.exe");
if (NT_SUCCESS(status)) {
DbgPrint("[Safe] Printf: %ws\n", buffer);
}
// ==================== RtlStringCbPrintfExW ====================
// 扩展格式化,返回剩余空间
PWSTR pEnd;
status = RtlStringCbPrintfExW(buffer, sizeof(buffer),
&pEnd, &remaining,
0,
L"Part1 ");
if (NT_SUCCESS(status)) {
// 继续在剩余空间写入
RtlStringCbPrintfW(pEnd, remaining, L"Part2");
DbgPrint("[Safe] Extended: %ws\n", buffer);
}
// ==================== RtlStringCbLengthW ====================
// 安全获取长度
size_t length;
status = RtlStringCbLengthW(buffer, sizeof(buffer), &length);
if (NT_SUCCESS(status)) {
DbgPrint("[Safe] Length: %zu bytes\n", length);
}
}
// 安全的UNICODE_STRING操作
NTSTATUS SafeUnicodeStringPrintf(
PUNICODE_STRING DestString,
PCWSTR Format,
...
) {
NTSTATUS status;
va_list args;
if (!DestString->Buffer || DestString->MaximumLength == 0) {
return STATUS_INVALID_PARAMETER;
}
va_start(args, Format);
status = RtlStringCbVPrintfW(
DestString->Buffer,
DestString->MaximumLength,
Format,
args
);
va_end(args);
if (NT_SUCCESS(status)) {
size_t length;
RtlStringCbLengthW(DestString->Buffer,
DestString->MaximumLength,
&length);
DestString->Length = (USHORT)length;
}
return status;
}
5.5、示例5:字符串查找和操作
// StringSearch.c - 字符串查找
#include <ntddk.h>
// 查找子字符串
BOOLEAN FindSubstring(PUNICODE_STRING String, PUNICODE_STRING SubString) {
USHORT i, j;
if (SubString->Length > String->Length) {
return FALSE;
}
for (i = 0; i <= (String->Length - SubString->Length) / sizeof(WCHAR); i++) {
BOOLEAN found = TRUE;
for (j = 0; j < SubString->Length / sizeof(WCHAR); j++) {
if (String->Buffer[i + j] != SubString->Buffer[j]) {
found = FALSE;
break;
}
}
if (found) {
return TRUE;
}
}
return FALSE;
}
// 查找子字符串(不区分大小写)
BOOLEAN FindSubstringNoCase(PUNICODE_STRING String, PUNICODE_STRING SubString) {
UNICODE_STRING upperString = {0};
UNICODE_STRING upperSubString = {0};
BOOLEAN result = FALSE;
NTSTATUS status;
// 分配大写副本
status = RtlUpcaseUnicodeString(&upperString, String, TRUE);
if (!NT_SUCCESS(status)) return FALSE;
status = RtlUpcaseUnicodeString(&upperSubString, SubString, TRUE);
if (!NT_SUCCESS(status)) {
RtlFreeUnicodeString(&upperString);
return FALSE;
}
result = FindSubstring(&upperString, &upperSubString);
RtlFreeUnicodeString(&upperString);
RtlFreeUnicodeString(&upperSubString);
return result;
}
// 字符串前缀检查
BOOLEAN HasPrefix(PUNICODE_STRING String, PUNICODE_STRING Prefix) {
return RtlPrefixUnicodeString(Prefix, String, TRUE);
}
// 字符串后缀检查
BOOLEAN HasSuffix(PUNICODE_STRING String, PUNICODE_STRING Suffix) {
if (Suffix->Length > String->Length) {
return FALSE;
}
UNICODE_STRING endPart;
endPart.Buffer = String->Buffer +
(String->Length - Suffix->Length) / sizeof(WCHAR);
endPart.Length = Suffix->Length;
endPart.MaximumLength = Suffix->Length;
return RtlEqualUnicodeString(&endPart, Suffix, TRUE);
}
// 测试
VOID TestStringSearch() {
UNICODE_STRING str, sub;
RtlInitUnicodeString(&str, L"C:\\Windows\\System32\\notepad.exe");
RtlInitUnicodeString(&sub, L"System32");
if (FindSubstringNoCase(&str, &sub)) {
DbgPrint("[Search] Found 'System32' in path\n");
}
RtlInitUnicodeString(&sub, L".exe");
if (HasSuffix(&str, &sub)) {
DbgPrint("[Search] Path ends with .exe\n");
}
RtlInitUnicodeString(&sub, L"C:\\Windows");
if (HasPrefix(&str, &sub)) {
DbgPrint("[Search] Path starts with C:\\Windows\n");
}
}
6、课后作业
- 实现一个字符串解析器,解析路径中的目录和文件名
- 编写安全的字符串格式化函数
- 实现字符串分割功能
- 处理用户传入的字符串并进行验证
7、扩展阅读
- MSDN: Rtl String Functions
- ntstrsafe.h头文件
- Unicode在Windows中的实现