一、什么是内核对象
我们在windows开发中经常会遇到内核对象,如事件(Event),管道(Pipe),互斥量(Mutex),完成端口,进程,线程等,他们都是内核对象。这些内核对象虽然通过不同的系统API来创建,但这些API都有一个共同特点,就是都需要传入SECURITY_ATTRIBUTES
安全描述符结构体指针,并且返回句柄(HANDLE)。依据这个特点,我们有一个简单方法来判断对象是否是内核对象,就是看创建它的函数是否允许传入SECURITY_ATTRIBUTES
安全描述符。
二、内核对象的创建
大多数创建内核对象的系统API函数,如CreateEvent, CreateMutex, CreateThread, CreateProcess, CreatePipe, CreateNamedPipe等都会返回一个HANDLE(无论是以返回值的形式,还是以指针参数的形式返回),创建内核对象成功时HANDLE为非NULL
,我们可以通过将HANDLE的值与NULL进行比较,来判断函数是否执行成功。但是有些函数比较例外,如CreateFile,这些函数执行失败时,返回的HANDLE等于INVALID_HANDLE_VALUIE
.
三、内核对象的销毁
3.1 引用计数
内核对象的所有者是操作系统内核,而不是创建它的进程。
多个进程可以引用(使用)同一个内核对象,操作系统使用了计数器的方式来管理内核对象(这个和C++中的std::shared_ptr
智能指针类似),一个内核对象其实有两个计数器:一个是给用户态(Ring3)用的句柄计数;另一个是指针计数,也叫引用计数,因为核心态程序(Ring0)也经常用到内核对象,为了使用方便,在核心态的代码用指针直接访问对象,所以内核对象的管理器也维护了这个指针引用计数。只有在内核对象的句柄计数
和引用计数
都为0时,该内核对象才被释放。一般而言,指针引用计数值比句柄计数值大。
3.2 获取内核对象的引用计数
虽然windows没有提供API让用户在用户态(Ring3)查询一个内核对象的句柄计数和引用计数,但我们可以从Ntdll.dll
导出NtQueryObject
函数来实现查询内核对象的当前状态(该函数没有被文档化)。
NtQueryObject
函数声明如下:
// 返回值:如果成功则返回0
//
DWORD WINAPI NtQueryObject(
HANDLE handle, // 待查询的句柄
DWORD nQueryIndex, // 0为查询对象的当前状态,包括句柄计数,引用计数等等。
VOID* pOutBuffer, // 存放查询结果
DWORD cbInBufferSize, // pOutBuffer的大小,如果nQueryIndex为0,这里为sizeof(SYSTEM_HANDLE_STATE)
VOID* cbOutBufferSize // 实际大小
);
将NtQueryObject
函数调用的细节封装到GetKernelObjectRefCount
函数中,方便使用:
bool GetKernelObjectRefCount(HANDLE handle, DWORD &handle_count, DWORD &point_count) {
typedef struct _SYSTEM_HANDLE_STATE {
DWORD r1;
DWORD GrantedAccess;
DWORD HandleCount; // 减1为句柄计数
DWORD ReferenceCount; // 减1为指针引用计数
DWORD r5;
DWORD r6;
DWORD r7;
DWORD r8;
DWORD r9;
DWORD r10;
DWORD r11;
DWORD r12;
DWORD r13;
DWORD r14;
}SYSTEM_HANDLE_STATE, *PSYSTEM_HANDLE_STATE;
typedef DWORD(WINAPI *PFN_NtQueryObject)(HANDLE handle,
DWORD nQueryIndex,
VOID* pOutBuffer,
DWORD cbInBufferSize,
VOID* cbOutBufferSize);
static PFN_NtQueryObject pfnNtQueryObject = NULL;
bool ret = false;
do {
if (pfnNtQueryObject == NULL) {
HMODULE ntdll = GetModuleHandle(TEXT("Ntdll.dll"));
if (ntdll == NULL)
break;
pfnNtQueryObject = (PFN_NtQueryObject)GetProcAddress(ntdll, "NtQueryObject");
if (pfnNtQueryObject == NULL)
break;
}
SYSTEM_HANDLE_STATE sys_handle_state;
memset(&sys_handle_state, 0, sizeof(SYSTEM_HANDLE_STATE));
DWORD out_buf_size = 0;
ret = (pfnNtQueryObject(handle, 0, &sys_handle_state, sizeof(SYSTEM_HANDLE_STATE), &out_buf_size) == 0);
if (ret) {
handle_count = sys_handle_state.HandleCount - 1;
point_count = sys_handle_state.ReferenceCount - 1;
}
} while (false);
return ret;
}