Windows内核驱动中UCHAR转WCHAR字符串的问题排查
解决Windows内核驱动中ANSI转Unicode字符串的写入只读内存错误
问题背景
在Windows内核驱动开发中,需将_SYSTEM_MODULE_INFORMATION结构体的UCHAR类型FullPathName字符串,分别复制到_DRIVER_INFO结构体的WCHAR类型ModuleName(取FullPathName + OffsetToFileName部分)和FullPathName字段。使用RtlAnsiStringToUnicodeString函数时,单独执行第一个转换正常,添加第二个转换后触发“attempted write to read only location”错误。
相关结构体定义
_DRIVER_INFO 结构体
typedef struct _DRIVER_INFO { PVOID MappedBase; PVOID ImageBase; ULONG ImageSize; WCHAR ModuleName[MAXIMUM_FILENAME_LENGTH]; WCHAR FullPathName[MAXIMUM_FILENAME_LENGTH]; } DRIVER_INFO;
_SYSTEM_MODULE_INFORMATION 结构体
typedef struct _SYSTEM_MODULE_INFORMATION { HANDLE Section; PVOID MappedBase; PVOID ImageBase; ULONG ImageSize; ULONG Flags; USHORT LoadOrderIndex; USHORT InitOrderIndex; USHORT LoadCount; USHORT OffsetToFileName; UCHAR FullPathName[MAXIMUM_FILENAME_LENGTH]; } SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;
错误代码问题分析
你提供的错误代码存在两个核心问题:
- 错误的类型转换:直接将
PCSTR指针强制转为PCANSI_STRING传入函数,而ANSI_STRING是包含Length、MaximumLength、Buffer的结构体,并非单纯字符指针,导致函数读取非法内存。 - 内存分配参数错误:第三个参数传
TRUE时,函数会尝试从堆分配内存存储转换后的Unicode字符串,但driverInfo的ModuleName和FullPathName是栈上固定大小数组,函数试图向不匹配的内存区域写入,触发只读内存错误。
正确解决方案
- 手动构造合法的
ANSI_STRING结构体,正确设置字符串长度和缓冲区指针。 - 调用
RtlAnsiStringToUnicodeString时,第三个参数传FALSE,指定使用我们提供的栈缓冲区,而非让函数分配堆内存。 - 处理转换失败的边界情况(如字符串过长),避免垃圾数据残留。
修正后的完整代码
for (ULONG i = 0; i < moduleList->NumberOfModules; i++) { PSYSTEM_MODULE_INFORMATION module = &moduleList->Modules[i]; DRIVER_INFO driverInfo = {0}; // 初始化结构体,清除垃圾数据 // 复制驱动基础信息 driverInfo.MappedBase = module->MappedBase; driverInfo.ImageBase = module->ImageBase; driverInfo.ImageSize = module->ImageSize; // 构造FullPathName对应的ANSI_STRING ANSI_STRING fullPathAnsi = {0}; fullPathAnsi.Buffer = (PCHAR)module->FullPathName; fullPathAnsi.Length = (USHORT)strlen((PCSTR)module->FullPathName); fullPathAnsi.MaximumLength = fullPathAnsi.Length + 1; // 包含字符串终止符 // 构造ModuleName对应的ANSI_STRING ANSI_STRING moduleNameAnsi = {0}; moduleNameAnsi.Buffer = (PCHAR)(module->FullPathName + module->OffsetToFileName); moduleNameAnsi.Length = (USHORT)strlen((PCSTR)moduleNameAnsi.Buffer); moduleNameAnsi.MaximumLength = moduleNameAnsi.Length + 1; // 转换FullPathName到Unicode,使用栈缓冲区 UNICODE_STRING fullPathUnicode = {0}; fullPathUnicode.Buffer = driverInfo.FullPathName; fullPathUnicode.MaximumLength = sizeof(driverInfo.FullPathName); NTSTATUS status = RtlAnsiStringToUnicodeString(&fullPathUnicode, &fullPathAnsi, FALSE); if (!NT_SUCCESS(status)) { DebugMessage("FullPath转换失败,状态码: 0x%X\n", status); // 手动添加终止符,避免垃圾数据 driverInfo.FullPathName[sizeof(driverInfo.FullPathName)/sizeof(WCHAR)-1] = L'\0'; } // 转换ModuleName到Unicode UNICODE_STRING moduleNameUnicode = {0}; moduleNameUnicode.Buffer = driverInfo.ModuleName; moduleNameUnicode.MaximumLength = sizeof(driverInfo.ModuleName); status = RtlAnsiStringToUnicodeString(&moduleNameUnicode, &moduleNameAnsi, FALSE); if (!NT_SUCCESS(status)) { DebugMessage("ModuleName转换失败,状态码: 0x%X\n", status); driverInfo.ModuleName[sizeof(driverInfo.ModuleName)/sizeof(WCHAR)-1] = L'\0'; } systemDrivers[i] = driverInfo; DebugMessage("Driver Path: %ws\n", driverInfo.FullPathName); DebugMessage("Driver Name: %ws\n", driverInfo.ModuleName); }
额外注意事项
- 必须初始化
DRIVER_INFO结构体,避免未初始化内存引发的异常。 - 依赖
strlen计算长度时,需确保内核返回的FullPathName以\0结尾(通常内核返回的结构体满足该条件)。 - 转换前需明确
UNICODE_STRING的缓冲区和最大长度,确保函数写入到我们指定的栈数组中。
内容的提问来源于stack exchange,提问作者mmorg
相关产品推荐
相关产品推荐

