Windows对象管理
对象管理是Windows Executive的一个子系统实现,用于管理Windows资源。 资源包括物理设备、文件、文件目录、注册表条目、正在运行的进程等等。所有子系统访问资源都必须通过对象管理子系统。
体系结构
Windows NT操作系统体系结构中,对象管理子系统管理所有的资源。资源被表示为对象。对象管理子系统保持着对每个对象的引用计数。任何访问对象的系统调用都必须通过对象管理子系统。Windows对象可分为内核对象、用户对象、GDI对象:
- 用户对象(User interface object):支持窗口管理。每个对象仅有一个句柄,句柄不能复制或继承,不能引用其他用户会话中的进程的用户句柄。任何进程只要有对某个用户句柄的安全访问权限,即可以访问该用户对象,即用户对象在当前会话下是全局的。一个进程最多有 65536 个用户对象句柄。用户对象包括:快捷键表HACCEL、插入点HCaret、鼠标指针HCURSOR、DDE 会话、窗口钩子HOOK、图标 HICON、菜单 HMENU、窗口 HWND、窗口位置Window position。[1]
- GDI 对象:支持图形。每个对象仅有一个句柄,句柄为进程私有。一个进程最多有 65536 个 GDI 对象句柄。GDI 对象包括:位图 HBITMAP、画刷 HBRUSH、设备环境HDC、增强型图元文件(EMF)、EMF 设备环境、字体 HFONT、内存 DC、图元文件Metafile、图元文件 DC、调色板 HPALETTE、画笔 HPEN、区域(Region)HRGN。[2]
- 内核对象:支持内存管理、进程执行、进程间通信。内核对象句柄是进程私有的,必须创建或者打开内核对象以获取其句柄。当进程创建或打开内核对象时,进程的句柄表中增加一个条目指向内核对象实例。进程的句柄表的索引称为句柄(handle)[3]。对象管理子系统使用句柄与命名两种方式管理对象实例。句柄在一个进程内部是线程共享的,但在进程之间不是直接可复用,需要特别方式在进程间传递对象句柄。一个进程任何时刻最多拥有 224,即大约 16,000,000 个句柄。句柄按照对象的分类可分为文件句柄、事件句柄、进程句柄等。一个进程对一个对象可以有多个句柄,以便按照不同权限来访问对象。
对象可分为内核对象(Kernel objects)与执行对象(Executive objects)。内核对象表示一些基本资源,如物理设备、同步服务等等。用户态的程序不能访问内核对象。[4]用户态的系统服务与应用程序使用执行对象,这是Windows Executive对外暴露的对象,用来封装一个或多个内核对象。执行对象还用于实现 NT 子系统或 POSIX 子系统的一些功能。
Windows NT 暴露的执行对象包括:
类别 | 描述 | 创建 / 获取句柄系统调用 | 创建 / 获取句柄函数 | 释放句柄函数 | 未通知状态 | 通知状态 | 等待成功的副作用 |
---|---|---|---|---|---|---|---|
目录 | 用来存放内核对象。多级嵌套的目录将所有内核对象组织成一个树形结构 | NtCreateDirectoryObject NtOpenDirectoryObject |
无 | ||||
进程 | 线程的集合,拥有共同的虚拟内存空间与控制信息 | NtCreateProcess NtOpenProcess |
CreateProcess OpenProcess GetCurrentProcess |
CloseHandle TerminateProcess |
进程仍然活动时 | 进程终止运行时 (TerminateProcess ExitProcess) |
无 |
线程 | 进程内部,执行程序的实体。 | NtCreateThread NtOpenThread |
CreateThread CreateThreadEx OpenThread GetCurrentThread |
CloseHandle TerminateThread |
线程仍然活动时 | 线程终止运行时 (TerminateThread ExitThread) |
无 |
作业 | 进程的集合 | NtCreateJobObject NtOpenJobObject |
CreateJobObject | CloseHandle | 当作业的时间尚未结束时 | 当作业的时间已经结束时 | 无 |
文件 | 一个打开的计算机文件或 I/O 设备。 | NtCreateFile NtOpenFile |
CreateFile | CloseHandle DeleteFile |
当 I/O 请求正在处理时 | 当 I/O 请求处理完毕时 | 无 |
文件映射对象 | 一块内存区域,映射到一个文件。 | NtCreateSection NtOpenSection |
CreateFileMapping | CloseHandle | |||
访问令牌 | 一个对象的访问权。 | NtCreateToken NtDuplicateToken NtOpenProcessToken NtOpenThreadToken |
CreateRestrictedToken DuplicateToken DuplicateTokenEx OpenProcessToken OpenThreadToken |
CloseHandle | |||
事件 | 封装了某些信息的一个对象,用于通知某些进程。 | NtCreateEvent NtOpenEvent |
CreateEvent CreateEventEx OpenEvent |
CloseHandle | ResetEvent,或 PulseEvent,或自动重置事件等待成功 | 当调用 SetEvent,或 PulseEvent | 自动重置事件等待成功后将自动重置 |
信号量 | 用于串行化访问某些资源的对象。 | NtCreateSemaphore NtOpenSemaphore |
CreateSemaphore CreateSemaphoreEx OpenSemaphore |
CloseHandle | 当数量 <=0 时 | 当数量 >0 时(ReleaseSemaphore) | 数量减 1 |
互斥锁 | 用于串行化访问某些资源的对象。 | 无 | CreateMutex CreateMutexEx OpenMutex |
CloseHandle | 被其他线程拥有时 | 未被其他线程拥有时 | 等待成功的线程获得 CPU 所有权 |
临界区域 | 使得指定的代码段被串行执行 | 无 | InitializeCriticalSection InitializeCriticalSectionAndSpinCount |
DeleteCriticalSection | 被其他线程拥有时(试图 EnterCriticalSection) | 未被其他线程拥有时(LeaveCriticalSection) | 等待成功的线程获得 CPU 所有权 |
定时器 | 按照固定时间间隔通知某些进程的对象。 | NtCreateTimer NtOpenTimer |
CreateWaitableTimer CreateWaitableTimerEx |
CloseHandle | CancelWaitableTimer 或自动重置定时器等待成功 | 当时间到时(SetWaitableTimer) | 自动重置定时器等待成功将 reset |
Timer queue | 无 | ||||||
Timer-queue timer | 无 | ||||||
注册表键值 | Windows 注册表条目的键值,数据类型不是 HANDLE 而是 HKEY | RegCreateKeyEx RegOpenKeyEx |
RegCloseKey | ||||
桌面 | 包含 GUI 元素的一个逻辑显示面,数据类型是 HDESK | 无 | CreateDesktop OpenDesktop GetThreadDesktop |
CloseDesktop | |||
WindowStation | 包含一些桌面对象、一块剪贴板对象、以及其他对象的对象,数据类型是 HWINSTA | 无 | CreateWindowStation OpenWindowStation GetProcessWindowStation |
CloseWindowStation | |||
剪贴板 | 用于其它对象的临时存储空间。 | 无 | OpenClipboard | CloseClipboard | |||
符号链接 | 对其他对象的引用 | NtCreateSymbolicLinkObject NtOpenSymbolicLinkObject |
|||||
事件日志 | 无 | OpenEventLog RegisterEventSource OpenBackupEventLog |
CloseEventLog | ||||
目录下更改通知 | 若指定的目录下发生了文件名、属性等更改,则唤起本事件 | FindFirstChangeNotification | FindCloseChangeNotification | 没有发生指定监视类型的修改 | 发生了指定监视类型的修改 | 重置事件状态;若需要监视下一次更改,使用循环搭配 FindNextChangeNotification | |
堆内存 | 无 | HeapCreate GetProcessHeap GetProcessHeaps |
HeapDestroy 不应释放进程的缺省堆 |
||||
I/O 完成端口 | NtCreateIoCompletion NtOpenIoCompletion |
CreateIoCompletionPort | CloseHandle | ||||
Mailslot | NtCreateMailslotFile |
CreateMailslot | CloseHandle | ||||
内存资源通知 | CreateMemoryResourceNotification | CloseHandle | |||||
模块 | 只有自己 LoadLibrary 加载的 DLL 才需要释放;数据类型为 HMODULE;WinMain 函数的 hInstance 参数是指向 EXE 主程序的 HMODULE(数据类型为 HINSTANCE) | 无 | LoadLibrary LoadLibraryEx GetModuleHandle GetModuleHandleEx WinMain 函数的 hInstance 参数 |
FreeLibrary | |||
管道 | 管道可以是双工(双向传输)的;创建管道的一方称为服务端,连接到已存在或将要存在的管道的一方称为客户端 | NtCreateNamedPipeFile | CreateNamedPipe CreatePipe CreateFile |
CloseHandle DisconnectNamedPipe |
|||
套接字 | Unix 下的套接字只是普通 fd,而 Windows 下的套接字一般而言和普通(文件系统)文件不一样,推荐使用专门的套接字 API 操作;数据类型是 SOCKET(UINT_PTR) | 无 | WSASocket WSAAccept |
closesocket | 与文件类似 | ||
资源更新 | 使用 UpdateResource 更新某个 PE 模块中的资源(Resource,包括字符串、图标等) | 无 | BeginUpdateResource | EndUpdateResource |
对象结构
每个被对象管理子系统所管理的对象,包含头部和体部。头部是对象管理子系统使用的状态信息。体部是对象相关的数据与暴露的服务。
对象头部对外暴露的信息称为Properties
, 包括:
Object Name
,用来标识对象Object Directory
,对象所属类别Security Descriptors
,对象的访问权限,一般在创建对象时传入,大多数时候传入值为NULL,表示采用默认安全属性。Quota Charges
,对象的资源使用信息Open handle count
,打开的句柄计数Open handle list
,活动引用的进程列表Reference count
,活动引用进程的计数Type
,用来标识对象体部结构
对象管理子系统所管理的对象必须提供下属服务:
Close
,关闭对象的一个句柄Duplicate
,创建对象的另一个句柄,用来给另一个进程共享访问该对象Query object
,获得对象的属性与性质等信息Query security
,得到对象的安全描述信息Set security
,改变对象的安全访问信息Wait
,同步一个或多个对象,通过特定事件。
同一类型的对象具有一些相同的属性,如类型名、是否分配在非分页内存、访问权限、同步信息等。这些由一个类型对象(type object)来表示。所有同一类型的对象实例共享这唯一的类型对象。 可以创建新的对象类型,这通过把一个对象的属性作为对外暴露的状态,把其方法作为对外暴露的服务来实现。
对象名(Object name
)是一个对象的描述性标识。对象管理子系统保持一个已经用于表示对象的名字列表,映射每个名字到对象实例。实际上大多数访问对象的行为是通过句柄;通过对象名来查找对象实例仅发生在创建对象时、跨进程共享一个对象时。
Object directories
用于按照类型来分类对象。预定义的Object directories
包括:
\??
(Win32 设备名,其中只有符号链接)\BaseNamedObjects
(互斥、事件、信号量、可等待计时器和段对象)\Callback
(回调函数)\Device
(设备)\Drivers
\FileSystem
\KnownDlls
\Nls
(language tables)\ObjectTypes
(对象类型对象)\RPC Controls
(RPC端口)\Security
(安全子系统对象)\Windows
(窗口子系统对象)
对象属于命名空间(Namespace). 每个用户会话(user session)是一个名字空间。这使得多个客户同时运行一个应用程序而不会发生干扰。在所有名字空间共享的对象属于GLOBAL命名空间。例如,在Global命名空间中创建一个事件,名字叫CSAPP:
CreateEvent( NULL, FALSE, FALSE, "Global\\CSAPP" );
全局命名空间使得多个客户会话间的进程可以通信。例如,一个客户/服务器使用互斥锁来同步,服务器模块在全局命名空间创建一个互斥锁对象,然后客户进程使用"Global\"前缀来打开这个互斥锁对象。 客户进程可以明示使用 "Local\"前缀来在客户会话命名空间中创建对象。[5]
OBJECT_ATTRIBUTES 结构:
typedef struct _OBJECT_ATTRIBUTES{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PSECURITY_DESCRIPTOR SecurityDescriptor;
PSECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;
}OBJECT_ATTRIBUTES;
Attributes 成员域可以是 0,或下述标志的组合:
OBJ_INHERIT OBJ_PERMANANT OBJ_EXCLUSIVE OBJ_CASE_INSENSITIVE OBJ_OPENIF OBJ_OPENLINK OBJ_KERNEL_HANDLE
参考文献
- Russinovich, Mark; David Solomon. . 4th edition. Microsoft Press. 2005: 124–149. ISBN 0-7356-1917-4.
- . [2014-01-08]. (原始内容存档于2017-09-18).
- . [2014-01-08]. (原始内容存档于2017-09-18).
- 每个进程中都存在一个句柄表,列出了所有本进程内可以使用的句柄。句柄表实际上是一个数组,每个数组元素为一个结构,包含一个指向内核对象的指针、访问掩码、继承标识等。句柄实际上是进程句柄表数组的索引。因此句柄是进程私有的。进程的句柄表的表头数据结构为HANDLE_TABLE。所有进程的句柄表表头形成一个List。句柄表的表项的数据结构为HANDLE_TABLE_ENTRY,长度为8字节,其中前四个数据为内核对象地址,后四个字节为访问掩码或在当前表项为空闲时存储下一空闲表项的索引值。
- . [2014-01-08]. (原始内容存档于2017-09-18).
- Kernel object namespaces