3.1 何为内核对象
3.1.1 Windows平台上的3大对象
(1)分类
对象 |
描述 |
备注 |
GUI对象 |
也叫用户对象,一般是单线程访问,属于线程级的对象,如 加速键表(HACCEL)、插入记号(Caret)、光标(HCURSOR)、桌面(HDESK)、 钩子(HHOOK)、图标(HICON)、菜单(HMENU)、窗口(HWND)、窗口栈(HWINSTA) |
句柄值是系统唯一的,即一个进程可以通过该句柄值对另一个进程中的对象进行操作,如发送消息。 |
GDI对象 |
如DC、Pen、Font等,一般是单线程访问,属于线程级的对象 |
句柄值只在进程内有效 |
内核对象 |
如进程、线程、文件等,一般允许多进程、多线程访问,因为内核对象有严格的安全机制及多线程访问控制策略,属系统级的对象。 |
句柄值是表示该内核对象在句柄表里的一个索引(注意,只是个索引值),该句柄值是与进程相关的,同一个对象在不同进程中可能有不同的索引。 |
(2)内核对象——所有者为操作系统内核,而非进程
内核对象 |
对象名称 |
句柄 类型 |
创建函数 |
销毁函数 |
Access token |
访问令牌 |
HANDLE |
CreateRestrictedToken, DuplicateToken(Ex), OpenProcessToken, OpenThreadToken |
CloseHandle |
Change notification |
文件、目录变更通知 |
HANDLE |
FindFirstChangeNotification |
FindCloseChangeNotification |
Communications device |
串口通信 |
HANDLE |
CreateFile |
CloseHandle |
Console input |
控制台输入 |
HANDLE |
CreateFile,with CONIN$ |
CloseHandle |
Console screen buffer |
控制台输出 |
HANDLE |
CreateFile,with CONOUT$ |
CloseHandle |
Desktop |
桌面 |
HDESK |
GetThreadDestop |
应用程序无法删除该对象 |
Event |
事件 |
HANDLE |
CreateEvent, CreateEventEx, OpenEvent |
CloseHandle |
Event log |
事件日志 |
HANDLE |
OpenEventLog, RegisterEventSource, OpenBackupEventLog |
CloseEventLog |
File |
文件 |
HANDLE |
CreateFile |
CloseHandle, DeleteFile |
File mapping |
文件映射 |
HANDLE |
CreateFileMapping, OpenFileMapping |
CloseHandle |
Find file |
文件查找 |
HANDLE |
FindFirstFile |
FindClose |
Heap |
堆 |
HANDLE |
HeapCreate |
HeapDestroy |
I/O completion port |
完成端口 |
HANDLE |
CreateIoCompletionPort |
CloseHandle |
Job |
作业 |
HANDLE |
CreateJobObject |
CloseHandle |
Mailslot |
邮槽 |
HANDLE |
CreateMailslot |
CloseHandle |
Memory resource notification |
内存资源通知 |
HANDLE |
CreateMemoryResourceNotification |
CloseHandle |
Module |
模块 |
HMODULE |
LoadLibrary, GetModuleHandle |
FreeLibrary |
Mutex |
互斥 |
HANDLE |
CreateMutex, CreateMutexEx, OpenMutex |
CloseHandle |
Pipe |
管道(命名、匿名) |
HANDLE |
CreateNamedPipe, CreatePipe |
CloseHandle, DisconnectNamedPipe |
Process |
进程 |
HANDLE |
CreateProcess, OpenProcess, GetCurrentProcess |
CloseHandle, TerminateProcess |
Semaphore |
信标量 |
HANDLE |
CreateSemaphore, CreateSemaphoreEx, OpenSemaphore |
CloseHandle |
Socket |
套接字 |
SOCKET |
socket, accept,WSASocket |
closesocket |
Thread |
线程 |
HANDLE |
CreateThread, CreateRemoteThread, GetCurrentThread |
CloseHandle, TerminateThread |
Timer |
计时器 |
HANDLE |
CreateWaitableTimer, CreateWaitableTimerEx, OpenWaitableTimer |
CloseHandle |
Update resource |
更新资源 |
HANDLE |
BeginUpdateResource |
EndUpdateResource |
Window station |
窗口栈 |
HWINSTA |
GetProcessWindowStation,CreateWindowStation |
CloseWindowStation |
3.1.2 内核对象的安全性
(1)安全描述符SD(SECURITY_DESCRIPTOR)
字段 |
描述 |
UCHAR Revision |
|
UCHAR Sbz1 |
|
SECURITY_DESCRIPTOR_CONTROL Control; |
一个控制位集合,说明安全描述符的含义或它每个成员 |
PSID Owner |
拥有者的安全ID |
PSID Group |
基本组对象SID |
PACL Sacl |
系统访问控制链表,当一个进程常识访问一个安全对象的时候,系统检查对象的 DACL 中的访问控制实体来决定是否赋予访问权限。 |
PACL Dacl |
DACL指定特殊用户或组的允许或拒绝的访问权限 |
★程序不需要直接操作安全描述符的内容。Windows API提供设置和返回安全描述符号的函数。另外,有用来创建和初始化一个新对象安全描述符号的函数。
(2)SECURITY_ATTRIBUTES结构体——创建内核对象几乎都要传此参数。
字段 |
描述 |
LPVOID nLength |
结构体大小 |
lpSecurityDescriptor |
指向安全描述符SD的指针 |
BOOL bInheritHandle; |
是否继承父进程可继承的对象句柄 |
3.2 进程内核对象句柄表
3.2.1 进程句柄表(Handle Table)的结构
(1)进程句柄表仅供内核对象使用,不适用于用户对象或GDI对象
(2)进程初始化时,句柄表为空,当创建一个内核对象时,加入句柄表的记录项。
(3)进程句柄表的结构
索引 |
指向内核对象内存块的指针 |
访问掩码(包含标志位的DWORD) |
标志 |
1 |
0x???????? |
0x???????? |
0x???????? |
2 |
0x???????? |
0x???????? |
0x???????? |
…… |
…… |
…… |
…… |
3.2.2 创建一个内核对象
(1)创建内核对象的函数:如CreateThread、CreateFile、CreateSemaphore等。(在32位系统中,内核对象的内容被保存在0x80000000至0xFFFFFFFF的这个内核地址空间中)
(2)返回值,是个句柄,表示该内核对象的在句柄表中的索引。(注意因句柄表可能会分层,所以该句柄最后两位(共32位)表示该对象在句柄表中所在的层数,因此如果要得到实际的索引值,必将该句柄值右移2位,见《Windows内核原理与实现·潘爱民》,p131图3.4)
(3)创建失败时,返回0(NULL),这也是句柄表中第1个内核对象的索引值为1,而为是0的原因。(注意句柄值是实际的索引值的4倍,所以第1个对象的返回的句柄值是4)。但有些函数的返回值是-1(INVALID_HANDLE_VALUE),如CreateFile,使用时请参考MSDN说明。
(4)调用一个内核对象时,在该函数内部会查找进程的句柄表,获得目标内核对象地址来操作对象的数据结构,如果传入的是一人无效句柄,调用失败。GetLastError将返回ERROR_INVALID_HANDLE。
3.2.3 关闭内核对象
(1)CloseHandle内部发生的事情
①首先检查进程的句柄表,验证传进来的函数该句柄的访问权限
②如果句柄有效,获得该内核对象的内存地址,并将该内核对象的“使用计数”减1
③如果计数变为0,将内核对象销毁。
(2)返回值:
成功 |
TRUE |
失败 |
当进程是正常运行的,返回FALSE; 当进程正在被调试,返回EEEOR_INVALID_HANDLE,便于调试错误。 |
(3)CloseHandle的几点说明
①内核对象的生命期可能长于进程的生命期。CloseHandle只是表明这个句柄不再使用了,但内核对象并不一定被销毁(可能被其他线程引用)。无论内核对象是否被销毁,都不要再试图用这个值。当 CloseHanle后,一般要将该值设为NULL。
②如果不小心再次使用这个句柄值来调用内核对象,则将可能发生如下意外情况:
A、当句柄表中的记录项己被完全删除时,会收到一个无效参数的错误报告。
B、当句柄表中的记录项被删除后,该项被新加入的内核对象重新填补,如果新内核对象与旧的内核对象类型不同,则会定位到该对象,但会报错。如果类型相同,这里并不会报错,但应用程序可能出现难于预料的结果,甚至应用程序状态被损坏,而无法恢复。
C、如果该记录未被删除(即,使用计数不为0,说明有其他线程调用),则会继续定位到该对象,但这很不安全。因为我们己经调用CloseHanle了,所以该对象不再受我们的控制,它将在什么时候被销毁,我们并不知道。这时如果这时另一线程也调用CloseHandle,则会出现内核对象被销毁,我们就会引用了一个被销毁的对象,导致程序出错。
④如果忘记调用CloseHandle,在进程运行期间会导致内存泄漏,但程序结束后,该对象仍会被系统正确清除。