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

打开Session0内核对象调用NtNotifyChangeSession返回无效句柄排查

提问内容

这是我首次接触Windows内核对象,若涉及的术语或表述存在不准确之处,欢迎指正。

我尝试打开名为\KernelObjects\Session0的Session类型内核对象,并向其发送数据。

根据《Windows Internals》书中的描述:

KernelObjects [...] 包含用于通知内核池资源状态、特定操作系统任务完成的事件对象,还包含代表每个交互会话的Session对象(至少包含Session0),以及对应每个内存分区的Partition对象(至少包含MemoryPartition0)。

通过WinObj工具可确认Session0对象确实存在:
WinObj截图

我编写了如下代码,实现三项核心逻辑:

  • 打开目标内核对象
  • 向对象发送用户数据
  • 关闭对象句柄
#include <winternl.h>
#include <stdio.h>


struct driverData
{
    [...] ;
};

typedef NTSTATUS(WINAPI* NTOpenEventAPI)(
    OUT PHANDLE EventHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes);

typedef NTSTATUS(WINAPI* NtNotifyChangeSession)(_In_ HANDLE SessionHandle, _In_ ULONG ChangeSequenceNumber, _In_ PLARGE_INTEGER ChangeTimeStamp,
    _In_ int Event, _In_ int NewState, _In_ int PreviousState,
    _In_reads_bytes_opt_(PayloadSize) PVOID Payload, _In_ ULONG PayloadSize);

typedef NTSTATUS(WINAPI* NtClose1)(_In_ HANDLE);


static
HANDLE OpenKernelSession(const wchar_t* event_name, OUT HANDLE* session) {
    static HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll");
    static const auto NtOpenSessionFunc = (NTOpenEventAPI)::GetProcAddress(ntdll, "NtOpenSession");
    if (!NtOpenSessionFunc) {
        fprintf(stderr, "Cannot load NtOpenSession function. Error: %u\n", ::GetLastError());
        return NULL;
    }

    static const auto RtlInitUnicodeStringFunc =
        reinterpret_cast<decltype(&::RtlInitUnicodeString)>(GetProcAddress(ntdll, "RtlInitUnicodeString"));
    if (!RtlInitUnicodeStringFunc) {
        fprintf(stderr, "Cannot load RtlInitUnicodeString function. Error: %u\n", ::GetLastError());
        return NULL;
    }

    UNICODE_STRING object_name;
    RtlInitUnicodeStringFunc(&object_name, event_name);

    OBJECT_ATTRIBUTES object_attrib;
    InitializeObjectAttributes(&object_attrib, &object_name, 0, nullptr, nullptr);


    NTSTATUS status = NtOpenSessionFunc(session, 2, &object_attrib);
    if (!NT_SUCCESS(status)) {
        fprintf(stderr, "Cannot open event %S. Error: 0x%08X\n", event_name, status);
        return NULL;
    }
    else {
        printf("Success opening object %S\n", event_name);
    }
    return session;
}

static
HANDLE notifyChangeSession(HANDLE* session) {
    static HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll");
    static const auto NtNotifyChangeSessionFunc = (NtNotifyChangeSession)::GetProcAddress(ntdll, "NtNotifyChangeSession");
    if (!NtNotifyChangeSessionFunc) {
        fprintf(stderr, "Cannot load NtNotifyChangeSession function. Error: %u\n", ::GetLastError());
        return NULL;
    }
   
    struct driverData drdata;

    NTSTATUS status = NtNotifyChangeSessionFunc(session, 0, 0, 1,
        4, 4, &drdata, sizeof(struct driverData));

    if (!NT_SUCCESS(status)) {
        fprintf(stderr, "Error calling NtNotifyChangeSessionFunc. Error: 0x%08X\n", status);
        return NULL;
    }
    else {
        printf("Success calling NtNotifyChangeSessionFunc\n");
    }

    return nullptr; // ToDo change  
}


static
int closeSession(HANDLE& session) {
    static HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll");
    static const auto NtCloseFunc = (NtClose1)::GetProcAddress(ntdll, "NtClose");
    if (!NtCloseFunc) {
        fprintf(stderr, "Cannot load NtCloseFunc function. Error: %u\n", ::GetLastError());
        return -1;
    }

    NTSTATUS status = NtCloseFunc(session);

    if (!NT_SUCCESS(status)) {
        fprintf(stderr, "Cannot close session\n");
        return -1;
    }
    else {
        printf("Success closing session\n");
    }

    return 0;
}

int main(int argc, char* argv[]) {

    HANDLE session = { 0 };
    OpenKernelSession(L"\\KernelObjects\\Session0", &session);

    notifyChangeSession(&session);

    Sleep(2000);

    return closeSession(session);
}

目前打开对象的操作可正常执行,但调用NtNotifyChangeSession时返回如下错误:

STATUS_INVALID_HANDLE  0xC0000008  An invalid HANDLE was specified.

我推测问题可能源于两种情况:

  • 上述代码存在编写错误:若属于该情况,希望协助定位具体bug位置;
  • 负责处理NtNotifyChangeSession所发送数据的驱动对传入数据做了校验,进而返回STATUS_INVALID_HANDLE错误:若属于该情况,希望了解如何定位执行该校验的代码逻辑,接受逆向二进制文件的排查方案。

在此先行感谢各位的帮助。


解答

你的代码里有两个直接触发STATUS_INVALID_HANDLE的编码错误:

  1. 句柄传参类型错误:NtNotifyChangeSession第一个参数要求传入HANDLE类型的句柄值本身,但你的notifyChangeSession入参是HANDLE*类型的句柄指针,调用时直接把指针地址传给了系统调用。内核将这个地址值当作句柄解析,必然判定为无效句柄。修正方式是调用时解引用指针,传入*session。
  2. 必填参数传空:NtNotifyChangeSession的第三个参数ChangeTimeStamp是必填输入参数,不是可选参数,你传入0(空指针)会触发内核内存访问违规,只是因为句柄校验逻辑先执行,所以暂时只返回句柄错误。修完第一个问题后需要定义LARGE_INTEGER类型变量存入合法时间戳,再传入变量地址。

还有几个潜在问题需要同步修正:

  • 函数指针语义错误:你将NtOpenSession的函数指针定义为NTOpenEventAPI,虽然当前函数签名匹配,但NtOpenSession不是打开事件对象的接口,传入的访问权限值2是事件对象的EVENT_MODIFY_STATE权限,不是Session对象的SESSION_MODIFY_ACCESS(当前值刚好同为0x2所以能打开,但后续系统版本调整权限定义会触发问题),建议使用正确的权限名和类型定义。
  • 校验对象类型:在WinObj中选中\KernelObjects\Session0,查看右侧详情栏的Type字段,确认其对象类型确实为Session。如果对象类型不匹配,即使打开成功,内核在NtNotifyChangeSession中校验句柄对应的对象类型时,也会返回STATUS_INVALID_HANDLE。
  • 参数类型不匹配:NtNotifyChangeSession的Event/NewState/PreviousState参数实际为ULONG类型,你定义为int在x86环境下可正常运行,但x64环境下传入负值会触发参数校验失败,建议修正函数指针的参数类型。
  • 冗余逻辑:notifyChangeSession函数中加载RtlInitUnicodeString的代码完全没有用到,可以直接删除。
  • 返回值类型错误:OpenKernelSession函数定义返回值为HANDLE,但实际返回的是入参session(HANDLE*类型),属于指针到句柄的隐式转换,虽然当前main函数没有使用该返回值所以没触发问题,但属于明显的编码错误,建议修正返回值逻辑,成功时返回打开的句柄值,失败返回NULL。

如果修完上述问题仍然返回错误,可按以下方式定位内核校验逻辑:

  • 配置双机内核调试环境,在WinDbg中对nt!NtNotifyChangeSession下断点,触发调用后单步跟踪内核执行流程。
  • 重点关注ObReferenceObjectByHandle调用的返回值:该函数负责校验句柄合法性、对象类型、访问权限,如果是这一步返回STATUS_INVALID_HANDLE,可查看传入的ObjectType参数是否为Session对象类型,传入的句柄值是否和用户态打开得到的句柄值一致。
  • 如果句柄校验通过,再继续跟踪后续逻辑,排查其他返回该错误码的校验分支。

内容的提问来源于stack exchange,提问作者user1618465

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 18:31:23