如何将Windows驱动中获取的UNICODE_STRING发送至用户态客户端?
问题描述
在Windows驱动中,通过PLOAD_IMAGE_NOTIFY_ROUTINE回调获取指定进程(如lsass.exe)的镜像路径(参数FullImageName为PUNICODE_STRING类型),需要将该路径发送到用户态客户端显示。尝试将FullImageName复制到IOCTL可访问的UNICODE_STRING中,但使用WCHAR相关复制函数未成功,请求解决方法。
附带的原代码如下:
原回调函数代码
void sSetNotifyRoutine(PUNICODE_STRING FullImageName, HANDLE ProcessId, PIMAGE_INFO ImageInfo) { UNREFERENCED_PARAMETER(ImageInfo); PEPROCESS process = NULL; PUNICODE_STRING processName; UNICODE_STRING lsass_prefix; RtlInitUnicodeString(&lsass_prefix, L"\\Device\\HarddiskVolume4\\Windows\\System32\\lsass.exe"); PsLookupProcessByProcessId(ProcessId, &process); SeLocateProcessImageName(process,&processName); if (compare_punicode(processName, &lsass_prefix) == 0) { DbgPrint("Ok\n"); DbgPrint("lsass launched %wZ\n", FullImageName); } //DbgPrint("%wZ (%d) loaded %wZ", processName, ProcessId, FullImageName); }
原IOCTL函数代码
NTSTATUS DeviceControl( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { UNREFERENCED_PARAMETER(DeviceObject); PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); NTSTATUS status = STATUS_SUCCESS; ULONG_PTR length = 0; switch (stack->Parameters.DeviceIoControl.IoControlCode) { case FIRST_DRIVER_IOCTL_TEST: if (lsass_string) { DbgPrint("[+] FIRST_DRIVER_IOCTL_TEST called\n"); if (stack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(CHAR*)) { status = STATUS_BUFFER_TOO_SMALL; DbgPrint("[!] STATUS_BUFFER_TOO_SMALL\n"); break; } else { if (lsass_string == NULL) { status = STATUS_INVALID_PARAMETER; DbgPrint("[!] STATUS_INVALID_PARAMETER\n"); break; } else { DbgPrint("sending to usermode\n"); RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, lsass_string, strlen(Irp->AssociatedIrp.SystemBuffer)); } } } break; default: status = STATUS_INVALID_DEVICE_REQUEST; DbgPrint("[!] STATUS_INVALID_DEVICE_REQUEST\n"); break; } Irp->IoStatus.Status = status; Irp->IoStatus.Information = length; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status; }
注:lsass_string和CHAR*仅为示例,非有效代码。
解决方案
1. 正确存储全局UNICODE_STRING
驱动中需定义全局UNICODE_STRING变量,并在回调中使用RtlDuplicateUnicodeString复制FullImageName——不能直接复制Buffer指针,因为回调中的Buffer可能被系统回收。同时要加自旋锁,避免多线程访问冲突。
2. IOCTL中正确处理Unicode字符串传输
- 用户态需传入足够大的缓冲区,容纳Unicode字符串的长度+终止符
- 驱动中检查输出缓冲区大小是否满足
UNICODE_STRING.Length + sizeof(WCHAR)(额外终止符确保用户态解析正常) - 使用
RtlCopyMemory复制内容,同时设置Irp->IoStatus.Information为实际复制的字节数
3. 同步与资源释放注意事项
- 全局变量访问加自旋锁,避免回调和IOCTL同时操作引发竞争
- 驱动卸载或不再需要路径时,调用
RtlFreeUnicodeString释放内存,避免泄漏
修改后的完整代码
全局变量定义
// 自旋锁,保护全局路径变量的访问 KSPIN_LOCK g_LsassPathLock; // 存储lsass的镜像路径 UNICODE_STRING g_LsassImagePath = {0};
初始化自旋锁(在DriverEntry中)
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); // 初始化自旋锁 KeInitializeSpinLock(&g_LsassPathLock); // ... 其他初始化代码(注册回调、创建设备等) return STATUS_SUCCESS; }
修改后的回调函数
void sSetNotifyRoutine(PUNICODE_STRING FullImageName, HANDLE ProcessId, PIMAGE_INFO ImageInfo) { UNREFERENCED_PARAMETER(ImageInfo); PEPROCESS process = NULL; UNICODE_STRING processName; UNICODE_STRING lsass_prefix; NTSTATUS status; RtlInitUnicodeString(&lsass_prefix, L"\\Device\\HarddiskVolume4\\Windows\\System32\\lsass.exe"); status = PsLookupProcessByProcessId(ProcessId, &process); if (!NT_SUCCESS(status)) { DbgPrint("PsLookupProcessByProcessId failed: 0x%X\n", status); return; } status = SeLocateProcessImageName(process, &processName); if (!NT_SUCCESS(status)) { DbgPrint("SeLocateProcessImageName failed: 0x%X\n", status); ObDereferenceObject(process); return; } // 比较进程路径 if (RtlCompareUnicodeString(&processName, &lsass_prefix, FALSE) == 0) { DbgPrint("Ok\n"); DbgPrint("lsass launched %wZ\n", FullImageName); // 加锁保护全局变量 KIRQL oldIrql; KeAcquireSpinLock(&g_LsassPathLock, &oldIrql); // 释放之前的内存(如果存在) if (g_LsassImagePath.Buffer != NULL) { RtlFreeUnicodeString(&g_LsassImagePath); RtlZeroMemory(&g_LsassImagePath, sizeof(UNICODE_STRING)); } // 复制FullImageName到全局变量,确保字符串以NULL结尾 status = RtlDuplicateUnicodeString(RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE, FullImageName, &g_LsassImagePath); if (!NT_SUCCESS(status)) { DbgPrint("RtlDuplicateUnicodeString failed: 0x%X\n", status); } KeReleaseSpinLock(&g_LsassPathLock, oldIrql); } ObDereferenceObject(process); }
修改后的IOCTL函数
NTSTATUS DeviceControl( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { UNREFERENCED_PARAMETER(DeviceObject); PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); NTSTATUS status = STATUS_SUCCESS; ULONG_PTR length = 0; switch (stack->Parameters.DeviceIoControl.IoControlCode) { case FIRST_DRIVER_IOCTL_TEST: { KIRQL oldIrql; KeAcquireSpinLock(&g_LsassPathLock, &oldIrql); // 检查全局路径是否已初始化 if (g_LsassImagePath.Buffer == NULL || g_LsassImagePath.Length == 0) { status = STATUS_NOT_FOUND; DbgPrint("[!] lsass path not found\n"); KeReleaseSpinLock(&g_LsassPathLock, oldIrql); break; } // 计算所需缓冲区大小:原长度+额外NULL终止符 ULONG requiredSize = g_LsassImagePath.Length + sizeof(WCHAR); if (stack->Parameters.DeviceIoControl.OutputBufferLength < requiredSize) { status = STATUS_BUFFER_TOO_SMALL; DbgPrint("[!] STATUS_BUFFER_TOO_SMALL, required: %d, provided: %d\n", requiredSize, stack->Parameters.DeviceIoControl.OutputBufferLength); KeReleaseSpinLock(&g_LsassPathLock, oldIrql); break; } // 复制字符串到用户态缓冲区 RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, g_LsassImagePath.Buffer, requiredSize); length = requiredSize; DbgPrint("sending to usermode: %wZ\n", &g_LsassImagePath); KeReleaseSpinLock(&g_LsassPathLock, oldIrql); } break; default: status = STATUS_INVALID_DEVICE_REQUEST; DbgPrint("[!] STATUS_INVALID_DEVICE_REQUEST\n"); break; } Irp->IoStatus.Status = status; Irp->IoStatus.Information = length; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status; }
驱动卸载时释放资源
VOID DriverUnload(PDRIVER_OBJECT DriverObject) { UNREFERENCED_PARAMETER(DriverObject); // 释放全局字符串内存 if (g_LsassImagePath.Buffer != NULL) { RtlFreeUnicodeString(&g_LsassImagePath); } // ... 其他卸载代码 }
内容的提问来源于stack exchange,提问作者UVision
相关产品推荐
相关产品推荐

