Windows内核安全

5、字符串操作

1、课程目标

  1. 掌握内核字符串类型和结构
  2. 学会使用RtlXxx字符串函数
  3. 理解ANSI和Unicode字符串转换
  4. 掌握安全的字符串处理方法

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、课后作业

  1. 实现一个字符串解析器,解析路径中的目录和文件名
  2. 编写安全的字符串格式化函数
  3. 实现字符串分割功能
  4. 处理用户传入的字符串并进行验证

7、扩展阅读

  • MSDN: Rtl String Functions
  • ntstrsafe.h头文件
  • Unicode在Windows中的实现