打开Session0内核对象调用NtNotifyChangeSession返回无效句柄排查
提问内容
这是我首次接触Windows内核对象,若涉及的术语或表述存在不准确之处,欢迎指正。
我尝试打开名为\KernelObjects\Session0的Session类型内核对象,并向其发送数据。
根据《Windows Internals》书中的描述:
KernelObjects [...] 包含用于通知内核池资源状态、特定操作系统任务完成的事件对象,还包含代表每个交互会话的Session对象(至少包含Session0),以及对应每个内存分区的Partition对象(至少包含MemoryPartition0)。
通过WinObj工具可确认Session0对象确实存在:
我编写了如下代码,实现三项核心逻辑:
- 打开目标内核对象
- 向对象发送用户数据
- 关闭对象句柄
#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的编码错误:
- 句柄传参类型错误:
NtNotifyChangeSession第一个参数要求传入HANDLE类型的句柄值本身,但你的notifyChangeSession入参是HANDLE*类型的句柄指针,调用时直接把指针地址传给了系统调用。内核将这个地址值当作句柄解析,必然判定为无效句柄。修正方式是调用时解引用指针,传入*session。 - 必填参数传空:
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
相关产品推荐
相关产品推荐

