欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Windows核心编程:(一)内核对象

程序员文章站 2022-06-03 18:26:25
...

内核对象

内核对象的管理

用户程序不能直接操作内核对象,内核对象由操作系统内核所有。用户程序要操作内核对象,需要使用函数来操作。

内核对象创建

使用create*函数创建内核对象,返回一个HANDLE。

对于每个进程,初始化时系统会分配一个句柄表(handle table),在其中维护一个个内核对象的句柄。包括:索引、指向内核对象内存块的指针、访问掩码(控制访问权限)、标志等。

在创建内核对象时,操作系统内核会为这个内核对象分配内存,然后查找主调进程的句柄表,找到一个 empty entry,然后对其进行初始化。

返回值

在调用内核对象创建函数时,如果创建失败(可能是内存不够,或者是遇到安全问题),那么就会返回一个句柄值。该句柄值可能是INVALID_HANDLE_VALUE或者是NULL。具体根据函数的不同而有不同的返回值。

内核对象关闭

BOOL CloseHandle(HANDLE hobject)

关闭流程如下:

  • 检查主调进程的句柄表,验证HANDLE hobject的值是有效的。

若指定的HANDLE是无效的,那么就会有以下两种情况:

  1. 如果该进程是正常运行的,那么将会返回FALSE。GetLastError()函数返回ERROR_INVALID_HANDLE。
  2. 如果该进程正在调试中,则抛出0xC0000008异常
  • 获取内核对象地址,访问内核对象。

  • 将内核对象使用计数递减。

  • 如果内核对象使用计数为0,销毁。

在这个过程中,同时也将清空句柄表中的对应项。

需要注意的是,调用CloseHandle之后同时也应该将传入的句柄参数hobject设为NULL。否则如果之后使用了这个HANDLE,要么因为句柄表对应位置为空,导致调用失败,返回ERROR_INVALID_HANDLE,更严重的可能会因为已经创建了一个新的内核对象,导致这个HANDLE指向的为一个完全不同的内核对象,到时出现难以预料的错误。

如果忘记关闭handle了呢?

进程在结束时,操作系统会自动释放已经分配的资源,包括其句柄表。在进程结束时,操作系统会扫描句柄表,然后将其中的HANDLE一个个释放,如果某个内核对象使用计数为0,那么就将其销毁。

内核对象的共享

Windows系统的内核对象是进程相关的。这样有两个好处:

  1. 可靠性。内核对象进程相关,一个进程就很难去干扰另一个进程的内核对象的运行,系统的稳定性和可靠性就会增加。
  2. 安全性。一个进程可以通过设置访问权限阻止其它进程操作自己的内核对象。

使用对象句柄继承

在父子进程之间继承对象句柄。(继承的是句柄,而不是对象)

父进程需要通过如下操作创建一个可以继承的句柄:

SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;

HANDLE hMutes = CreateMutex(&sa, FALSE, NULL);

以上代码初始化了一个SECURITY_ATTRIBUTES结构,然后设置bInheritHandle为TRUE;

如果在创建内核对象时,将NULL作为PSECURITY_ATTRIBUTES参数传入,则默认不可继承。如果将以上设置了bInheritHandleSECURITY_ATTRIBUTES传入,则内核对象是可以继承的。

然后使用BOOL CreateProcess()函数创建子进程,然后指定其中的bInheritHandles参数为TRUE。这样,在创建子进程时,操作系统就会遍历父进程的句柄表,然后将其中的可继承的项完全复制到子进程的句柄表。(包括作为索引的句柄值)。因此,子进程继承的父进程的内核对象,其句柄值是一致的。同时,操作系统还会自动增加内核对象的使用计数

传递句柄

子进程并不知道自己继承了哪些句柄,因此需要一种方式,让子进程能够知道自己继承了哪些句柄,并且将它利用起来。

有以下几种方式让父进程向子进程传递句柄:

  1. 命令行参数:父进程将句柄值作为命令行参数传递给子进程。子进程调用_stscanf_s() 函数解析命令行,获取句柄值。
  2. 利用进程间的通信机制。比如:
    1. 父进程等待子进程完成初始化(WaitForInputIdle()函数)。然后父进程向子进程发布消息。
    2. 父进程向环境块中添加环境变量,变量名称应该为子进程所知道的一个名称,变量的值为待继承的句柄值。然后,当父进程创建子进程,子进程继承父进程的环境变量,然后通过GetEnvironmentVariable 来获得继承到的内核对象的句柄值(子进程通过查询环境变量的名称,到环境块中查询,得到句柄值)。

改变句柄标志

使用函数SetHandleInformation()。如下:

BOOL SetHandleInformation(
	HANDLE hObject,
    DWORD dwMask,
    DWORD dwFlags
);

例如要打开继承标志:

SetHandleInformation(hObj, HANDLE_FLAG_INHERIT,HANDLE_FLAG_INHERIT);
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT,0); // CLOSE

要查看句柄表项的标志,可以使用:

BOOL GetHandleInformation(
	HANDLE hObject,
    PDWORD pdwFlags
);

则会在pdwFlags中存储标志值。

为内核对象命名

进程间也可以用对内核对象命名的方式来传递一个句柄。如:

进程A调用如下函数:

HANDLE hMutexProcessA = CreateMutex(NULL, FALSE, TEXT("JeffMutex"));

进程B要获取这个命名了的内核对象,只需要这样:

HANDLE hMutexProccessB = CreateMutex(NULL, FALSE, TEXT("JeffMutex"));

此时系统会查看内核对象的命名空间是否有一个叫做"JeffMutex"的对象,然后检查这个对象的类型,如果是要创建的对象类型(Mutex),则在进程B的句柄表中找到一个 empty entry,然后将其初始化,指向现有的内核对象。(这一步中不保留句柄值,因为可能B进程中已经存在了一个相同的句柄值。应该新生成一个句柄值)。同样,这个被共享的内核对象的使用计数会递增。

如果类型不匹配,或者内核对象的创建者不允许访问,则CreateMutex()函数就会执行失败,然后返回NULL。

也可以使用Open*函数来打开一个命名内核对象。

复制对象句柄

使用DuplicateHandle函数。

BOOL DuplicateHandle(
	HANDLE hSourceProcessHandle,
    HANDLE hSourceHandle,
    HANDLE hTargetProcessHandle,
    PHANDLE phTargetHandle,
    DWORD dwDesiredAccess,
    BOOL bInheritHandle,
    DWORD DwOptions
);

第1、3个参数是标识进程内核对象的句柄,分别为源进程和目标进程。该函数会将源进程中的句柄信息复制到目标进程中,即第二个参数hSourceHandle

第4个参数phTargetHandle是一个HANDLE变量的地址,用来保存复制后得到的HANDLE值。

第五个参数dwDesiredAccess指定访问掩码,如指定了DwOptionsDUPLICATE_SAME_ACCESS,则这一项会被忽略.

bInheritHandle控制是否能够继承。

当执行完DuplicateHandle,目标进程同样不知道自己拥有了一个内核对象。但是不能使用命令行,因为它已经启动了。所以需要通过其他进程间的通信机制通知它。

相关标签: Windows