windows核心编程-3-内核对象

3.1 什么是内核对象

   内核对象: 通过调用函数来创建的对象;如,CreateFileMapping函数 创建一个文件映射对象;

每个内核对象 都只是内核分配的一个内存块,并且只能由该内核访问。该内存块是一种数据结构,它的成员负责维护该对象的各种信息;

3.1.1 内核对象的使用计数

内核对象由内核所拥有,而不是由进程所拥有;

即,如果进程调用一个创建内核对象的函数,然后该进程中止了,那么内核对象不一定被撤销;

每个内核对象都包含一个使用计数,当一个内核对象刚创建时,使用计数为1;

当另一个进程访问一个现有的内核对象时,使用计数就递增 1;

 

3.1.2 安全性

   内核对象能够得到安全描述符的保护。

用于创建内核对象的函数,都有一个指向SECURITY_ATTRIBUTES结构的指针作为其参数,如:

 

HANDLE
WINAPI
CreateFileMapping(
    _In_ HANDLE hFile,
    _In_opt_ LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
    _In_ DWORD flProtect,
    _In_ DWORD dwMaximumSizeHigh,
    _In_ DWORD dwMaximumSizeLow,
    _In_opt_ LPCWSTR lpName
    );

如果该安全参数lpFileMappingAttributes指定为NULL,则可以创建带有默认安全性的内核对象; 也可以指定一个SECURITY_ATTRIBUTES结构,初始化,并传递该结构的地址给参数;SECIRITY_ATTRIBUTES结构如下:

typedef struct _SECURITY_ATTRIBUTES {
    DWORD nLength;
    LPVOID lpSecurityDescriptor;
    BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;

只是获得一个内核对象的访问权(而不是创建一个新对象),可以使用对应的Open*开头的函数,如OpenFileMapping;

除内核对象外,还有其他类型的对象,如菜单,窗口,鼠标光标,字体等;这些对象是用户对象或图形设备接口(GDI)对象;

判断一个对象是否是内核对象,可通过其创建函数中是否有用来设定安全属性 的参数来判断,也就是上面的SECIRITY_ATTRIBUTES;

 

3.2 进程的内核对象 句柄表

 当一个进程初始化时,系统会为它分配一个句柄表;该句柄表只用于内核对象,不用于用户对象或GDI对象;进程的句柄表只是个数据结构的 数组,该结构包含一个指向内核对象的指针,一个访问屏蔽和一些标志:

3.2.1 创建内核对象

1.  当进程被初次初始化时,它的句柄表是空的;

2.当进程中的线程调用创建内核对象的函数时,如CreateThread,内核为该对象分配一个内存块,并初始化;

3. 这时,内核对进程的句柄表进行扫描,找出一个  空项;

4.由于表3-1的句柄表是空的,内核便找到索引 1位置上的结构并对它进行初始化; 该指针成员被设置为内核对象的数据结构的地址,访问屏蔽设为全部访问权,标志也相应设置;

 

用于创建内核对象的函数,均返回与进程相关的句柄,这些句柄可以被相同进程中的任何线程加以使用;

该句柄值实际上是放入进程句柄表中的索引,它用于标识内核对象的信息存放的位置;

3.2.2 关闭内核对象

 无论怎样创建内核对象,都要通过 CloseHandle来结束对该对象的操作;

1. CloseHandle首先扫描进程的句柄表,检测传递给它的索引(句柄);

2. 如果索引有效,那么系统就可以获得内核对象的数据结构的地址,并确定该结构中的使用计数的数据成员;

3. 如果使用计数是0, 内核便从内存中撤销该内核对象;

 

3.3 跨越进程边界共享内核对象

许多情况下,不同进程中运行的线程需要共享内核对象;

进程共享内核对象的 3个不同的机制: 1. 继承对象句柄; 2.命名对象; 3.用DuplicateHandle函数复制对象句柄;

3.3.1 对象句柄的继承性

 只有当进程具有父子 关系时,才能使用对象句柄的继承性;实现这种继承性,父进程需要进行的操作:

1.当父进程创建内核对象时,需指明对象的句柄是个可继承的句柄;内核对象句柄有继承性,但内核对象本身没有继承性;

2. 要创建能继承的句柄,父进程必须指定一个SECURITY_ATTRIBUTES结构并对它进行初始化,然后将该结构的地址传递给create函数:

SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;//使用默认安全性
sa.bInheritHandle = TRUE;  //句柄可继承

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

对于进程句柄表项目中的标志,如果将 成员bInheritHandle置为TRUE,那么该标志被置为1;

3.父进程使用CreateProcess函数来生成子进程。

 

3.3.2 改变句柄的标志

父进程创建一个内核对象,生成两个子进程, 如果只想要一个子进程来继承内核对象的句柄,可以调用SetHandleInformation函数来改变内核对象的 标志:

WINBASEAPI
BOOL
WINAPI
SetHandleInformation(
    _In_ HANDLE hObject,
    _In_ DWORD dwMask,
    _In_ DWORD dwFlags
    );

第一个参数用于表示有效的句柄;

第二个参数dwMask告诉函数想要改变哪个或哪几个标志;目前有两个标志与句柄相关联:

#define HANDLE_FLAG_INHERIT             0x00000001
#define HANDLE_FLAG_PROTECT_FROM_CLOSE  0x00000002

第三个参数 dwFlags表明将该标志设置成什么值;如,打开一个内核对象的继承标志:

 SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);

关闭该标志: SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, 0);

 

对应的,GetHandleInformation函数的lpdwFlags参数返回 特定句柄的当前标志的设置值:

WINBASEAPI
BOOL
WINAPI
GetHandleInformation(
    _In_ HANDLE hObject,
    _Out_ LPDWORD lpdwFlags
    );

例如:了解句柄是否可继承:

 

DWORD dwFlags;
GetHandleInformation(hObj, &dwFlags);
BOOL fHandleIsInheritable = (0 != (dwFlags & HANDLE_FLAG_INHERIT));

3.3.3 命名对象

共享跨越进程边界的内核对象的第二种方法 -- 命名对象; 大部分内核对象都是可命名的;在创建函数,如

WINBASEAPI
HANDLE
WINAPI
CreateSemaphore(
    _In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
    _In_ LONG lInitialCount,
    _In_ LONG lMaximumCount,
    _In_opt_ LPCWSTR lpName
    );

最后一个参数 lpName为命名;当该参数为 NULL时,创建一个未命名(匿名)的内核对象;

当创建一个未命名的内核对象时,可以通过使用继承性(如上)或者DuplicateHandle函数来共享跨越进程的对象;如果要按名字共享 对象,必须为对象赋予名字;

 

按名字共享对象的另一种方法: 使用Open*函数,如:

HANDLE
WINAPI
OpenSemaphore(
    _In_ DWORD dwDesiredAccess,
    _In_ BOOL bInheritHandle,
    _In_ LPCWSTR lpName
    );

Create*函数和Open*函数的主要差别: 如果对象不存在,Create*函数将创建该对象, 而Open*函数则运行失败;

注意使用 GetLastError函数来获取函数运行的结果;

 

3.3.4 终端服务器的名字空间

终端服务器拥有内核对象的多个名字空间;

 

3.3.5 复制对象句柄

共享跨越进程边界的内核对象的第三种方法: 使用DuplicateHandle函数:

WINBASEAPI
BOOL
WINAPI
DuplicateHandle(
    _In_ HANDLE hSourceProcessHandle, //源进程句柄
    _In_ HANDLE hSourceHandle, //内核对象句柄,与源进程相关
    _In_ HANDLE hTargetProcessHandle, //目标进程句柄
    _Outptr_ LPHANDLE lpTargetHandle,//接收 输出的目标内核对象句柄
    _In_ DWORD dwDesiredAccess,//使用的访问屏蔽值
    _In_ BOOL bInheritHandle, //继承性标志
    _In_ DWORD dwOptions //组合值
    );

该函数取出一个进程句柄表中的项目,并将该项目拷贝到另一个进程的句柄表中;

dwOptions参数 可以是0 ,也可以是DUPLICATE_SAME_ACCESS和DUPLICATE_CLOSE_SOURCE的组合;

设定 DUPLICATE_SAME_ACCESS : 目标进程句柄 与 源进程句柄有 相同的访问屏蔽,将忽略dwDesiredAccess参数;

设定 DUPLICATE_CLOSE_SOURCE:  关闭源进程中的句柄;内核对象的使用计数 不变;

使用示例:

 

通常在涉及两个进程时才会用到DuplicateHandle函数:

对于用duplicateHandle函数获取到的目标 内核对象句柄 hObjInProcessT, 不能在Process S的上面这些代码里面调用CloseHandle(hObjInProcessT), 因为使用了非继承性,内核对象计数没有增加, 这个句柄要由进程T来关闭,进程S来关闭会导致各种不可预知的行为;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值