Windows 内核对象
每个内核对象只是内核分配的一个内存块,只能由该内核访问
安全性 :
用于创建内核对象的函数几乎都有一个指向 SECURITY_ATTRIBUTES 结构的指针作为参数
大多数程序为该参数传递 NULL ,默认安全性意味着对象的管理小组的任何成员 和 对象的创建者都拥有对该对象的全部访问权 , 而其他人均无权访问该对象
typeof struct _SECURITY_ATTRIBUTES{
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES;
SECURITY_ATTRIBUTES 结构体真正与安全有关的成员只有 lpSecurityDescriptor;
用于创建用户对象和GDI对象的函数都没有 PSECURITY_ATTRIBUTES 参数
进程的内核对象句柄表 :
当一个进程被初始化时 , 系统要为它分配一个句柄表 . 该句柄表只用于内核对象不用于用户对象 或 GDI 对象 .
句柄表只是一个数据结构的数组 , 每个结构都包含一个指向内核对象的指针 , 一个访问屏蔽 和 一些标志
进程的句柄结构 :
-----------------------------------------------------------------------------------------------------------
索引 内核对象内存块的指针 访问屏蔽(标志位的 DWORD) 标志(标志位的DWORD)
1 Ox???????? Ox???????? Ox????????
2 Ox???????? Ox???????? Ox????????
创建内核对象 :
当进程初次被初始化时 , 它的句柄表式空的 . 然后 , 当进程中的线程调用创建内核对象的函数时 , 内核就为该对象分配一块内存 , 并对它初始化 .
这时内核对进程的句柄表进行扫描 , 找出一个空项 。 由于句柄表初始化时空的 , 内核便找到索引 1 的位置上的结构并对它进行初始化 , 该指针成员
将被设置为内核对象的数据结构的内存地址 , 访问屏蔽设置为全部访问权 , 同事 , 各个标志也做了设置
用于创建内核对象的所有函数均返回与进程有关的句柄 , 这些句柄可以被在同一个进程中运行的任何或所有线程成功的加以使用 。 该句柄值实际上是
放入进程的句柄表中的索引 , 它用于标识内核对象的信息存放的位置 .
每当调用一个将内核对象句柄接受为参数的函数时 , 就要传递一个 Create*& 函数返回的值 。 从内部来说 , 该函数要查看进程的句柄表 , 以获取要
生成的内核对象 , 然后按定义的很好的方式来生成该对象的数据结构
关闭内核对象 :
无论怎样创建的内核对象 , 都有向系统指明将通过调用 CloseHandel 来结束对该对象的操作 : BOOL CloseHandle(HANDLE hobj);
CloseHandel 函数首先检查调用进程的句柄表 , 以确保传递给它的索引(句柄)用于标识一个进程实际上无权访问的对象。
跨越进程边界共享内核对象 :
文件映射对象使你能够在同一个机器上运行的两个进程之间共享数据块
邮箱和指定的管道使得应用程序能够在连网的不同机器上运行的进程之间发送数据块
互斥对象 , 信标 和 事件 使得不同进程中的线程能够同步它们的连续运行 , 这与一个应用程序在完成某项任务时需要将情况通知另一个应用程序的情况相同
对象句柄的继承性 :
只有当进程具有父子关系时 , 才能使用对象句柄的继承性 。
内核对象的句柄具有继承性 , 内核对象本身不具备继承性 。
(1)若要创建能够继承的句柄 , 父进程必须指定一个 SECURITY_ATTRIBUTES 结构并对它进行初始化 , 然后将该结构的地址传递给特定的 Create 函数
SECURITY_ATTRIBUTES sa;
sa.length = sizeof(sa);
sa.lpSecurityDescriptor = NULL ; //使用默认的安全级别
sa.bInheritHandle = TRUE ; //TRUE 创建并返回可以继承的对象句柄 , FALSE 创建并返回不可以继承的对象句柄
HANDLE hMutex = CreateMutex(&sa,FALSE,NULL);
创建内核对象的时候 , 如果传递 NULL 作为 PSECURITY_ATTRIBUTES 的参数 , 那么返回的句柄对象是不能继承的 , 并且标志位是 0 。如果将 bInheritHandle 成员设置为 TRUE , 那么该标志位将被置为 1 , 表示可以被继承
进程的句柄结构 :
-----------------------------------------------------------------------------------------------------------
索引 内核对象内存块的指针 访问屏蔽(标志位的 DWORD) 标志(标志位的DWORD)
1 Ox???????? Ox???????? Ox00000000 //不可以被继承
2 Ox???????? Ox???????? Ox00000001 //可以被继承
bInheritHandle 传递 TRUE 时
1 操作系统就创建新子进程 , 但是不允许子进程立即开始执行它的代码 。
2 系统为子进程创建一个新的和空的句柄表
3 遍历父进程的句柄表 , 找到有效的可继承的句柄的每个项目 , 系统会将该项目准确的拷贝到子进程的句柄表中
(2) 由父进程创建子进程 , 这要使用 CreateProcess 函数来完成
GetEnvironmentVariable 函数
获取被继承对象的句柄值。如果子进程要生成另一个子进程,那么使用这种方法是极好的,因
为环境变量可以被再次继承。
改变句柄的标志 :
有时候 , 父进程创建了一个内核对象 , 但是想控制有哪个子进程来继承内核对象句柄 , 这时就需要改变内核对象的 继承标志
SetHandleInformation 函数 :
BOOL SetHandleInformation(
HANDLE hObject, //标识一个句柄
DWORD dwMask, //要改变的标志 #define HANDLE_FLAG_INHERIT 0x00000001 ; #define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002
DWORD dwFlags);//指明想将该标志设置成什么值
HANDLEFLAGPROTECTFROMCLOSE标志用于告诉系统,该句柄不应该被关闭
如果一个线程视图关闭一个受保护的句柄 , CloseHandle 就会产生一个异常条件
GetHandleInformation 函数 :
BOOL GetHandleInformation(
HANDLE hObj,
PDWORD pdwFlags);
该函数返回 pdwFlags 指向的 DWORD 中特定句柄当前标志的设置值 , 若要了解句柄是否可继承 ,请使用以下代码 :
DWORD dwFlags;
GetHandleInformation(hObj,&dwFlags);
BOOL fHandleIsHeritable =(0!=(dwFlags & HANDLE_FLAG_INHERIT));
命名对象 :
共享跨越进程边界的内核对象的第二种方法就是给 对象命名 . 并不是全部内核对象都是可以命名的 .
HANDLE CreateMutex(
PSECURITY_ATTRIBUTES psa ,
BOOL bInitialOwner ,
PCTSTR pszName);
HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa ,
BOOL bManualReset ,
BOOL bInitialState ,
PCTSTR pszName);
HANDLE CreateSemaphore(
PSECURITY_ATTRIBUTES psa ,
LONG lInitialCount ,
LONG lMaximumCount ,
PCTSTR pszName);
HANDLE WaitableTimer(
PSECURITY_ATTRIBUTES psa ,
BOOL bManualResult ,
PCTSTR pszName);
HANDLE CreateFileMapping(
HANDLE hFile ,
PSECURITY_ATTRIBUTES psa ,
DWORD flProtect ,
DWORD dwMaximunSizeHigh ,
DWORD dwMaximunSizeLow ,
PCTSTR pszName);
HANDLE CreateJobObject(
PSECURITY_ATTRIBUTES psa ,
PCTSTR pszName);
以上函数的最后一个参数都是 pszName , 如果传递 NULL 那么就创建一个未命名的(匿名)内核对象 . 如果创建了一个未命名的(匿名)内核对象 , 可以通过使用继承性 或 DuplicateHandel 共享跨越进程的对象 . 若要按名字共享对象 , 必须为对象赋予一个名字 . 成功创建的命名内核对象是 互斥 的 , 不必是可继承的
内核对象命名如果不为 NULL , 则必须是一个以 0 结尾的字符串名字的地址 . 名字的最大长度是 MAX_PATH (260) 个字符 , 名字必须是唯一的 , 如果名字已经存在 , 系统会检查是否有访问权限 , 如果有访问权限 , 会在新的线程句柄列表中找出一个空的索引进行初始化 , 没有权限时返回的始终是 NULL
如果想要打开已有的内核对象 :
HANDLE OpenMutex(
DWORD dwDesiredAccess ,
BOOL bInitialHandle ,
PCTSTR pszName);
HANDLE OpenEvent(
DWORD dwDesiredAccess ,
BOOL bInitialHandle ,
PCTSTR pszName);
HANDLE OpenSemaphore(
DWORD dwDesiredAccess ,
BOOL bInitialHandle ,
PCTSTR pszName);
HANDLE OpenWaitableTimer(
DWORD dwDesiredAccess ,
BOOL bInitialHandle ,
PCTSTR pszName);
HANDLE OpenFileMapping(
DWORD dwDesiredAccess ,
BOOL bInitialHandle ,
PCTSTR pszName);
HANDLE OpenJobObject(
DWORD dwDesiredAccess ,
BOOL bInitialHandle ,
PCTSTR pszName);
调用 Create* 函数 与 调用 Open* 函数之间的主要差别是 , 如果对象并不存在 , Create* 函数将创建该对象 , 而 Open* 函数则运行失败
终端服务器的名字空间 :
终端服务器拥有内核对象的多个名字空间 . 服务程序的名字空间对象总是放在全局名字空间中。
HANDLE h = CreateEvent(NULL , FALSE , FALSE , "Global\\MyName"); //将内核对象放入全局的名字空间
HANDLE h = CreateEvent(NULL , FALSE , FALSE , "Local\\MyName); //放入会话的名字空间
复制对象句柄 :
共享跨越进程边界的内核对象的最后一个方法是使用 DuplicateHandle 函数
BOOL DuplicateHandle(
HANDLE hSourceProcessHandle ,
HANDLE hSourceHandle ,
HANDLE hTargetProcessHandle ,
PHANDLE phTargetHandle ,
DWORD dwDesiredAccess ,
BOOL bInheritHandle ,
DWORD dwOptions);
该函数取出一个进程的句柄表中的项目 , 并将该项目拷贝到另一个进程的句柄表中