You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 13:51:30