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

内核对象

内核对象的管理

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

内核对象创建

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值