您的位置:首页 > 其它

学习笔记之内核对象

2012-12-21 13:44 176 查看
内核对象的基本定义

作为windows软件开发人员,我们经常要创建、打开和处理内核对象。每个内核对象都只是一个内存块,它有操作系统分配,并只能由操作系统内核访问。这个内存块是一个数据结构,维护着与对象相关的信息。少数成员(安全描述符和使用计数等)是所有对象都有的,但大多数成员都是不同类型的内核对象所特有的。

由于内核对象的数据结构只能由操作系统内核访问,那我们如何操纵这些内核对象呢?答案是我们通过windows提供的一组函数,此组函数会返回一个句柄,它标示了内核对象的索引。我们通过这个句柄来管理和操纵内核对象。

为了增加操作系统的可靠性,这些句柄是进程相关的。

使用计数

每一个内核对象有有一个使用计数的成员,它维护内核对象在什么情况下被销毁。内核对象的所有者是操作系统,而不是进程,当进程创建内核对象,然后进程立即终止,内核对象不一定会被销毁。只有当内核对象的使用计数为0的时候,内核对象才会被销毁。最初一个进程创建内核对象时,内核对象的使用计数为1,而当另外一个进程获得了对现有内核对象的访问后,内核对象使用计数会递加1,所以当其中任何一个进程结束时,只会把内核对象当前使用计数递减1。所以进程结束时,内核对象不一定会销毁。

怎样区分内核对象

当我们利用函数创建一个对象时,怎么样才能区分我们创建的对象是否是内核对象呢?最主要是看函数的原型,每一个内核对象都包含一个SECURITY_ATTRIBUTES的安全描述符,当函数的参数拥有这个结构的指针时,当前函数创建的就是一个内核对象。

Typedef struct _SECURITY_ATTRIBUTES {

DWORD nLength;

LPVOID lpSecurityDescriptor;

BOOL bInheritHandle;

} SECURITY_ATTRIBUTES;

nLength:SECURITY_ATTRIBUTES结构的大小

lpSecurityDescriptor:安全描述符,可以通过CreateWellKnownSid函数来创建,也可以用

ConvertStringSecurityDescriptorToSecurityDescriptor函数来转换字符串为一个安全描述符

bInheritHandle:标识此句柄能否被继承

进程内核句柄表

每一个进程创建时,系统都会为它分配一个内核对象句柄表,它是一个由数据结构组成的数组。每一个结构都包含索引、指向一个内核对象的指针、一个访问掩码和一些标志。当我们创建内核对象时,系统会查找进程空白的句柄表记录项加以初始化(指针成员会被初始化为内核对象的数据结构的内部内存地址、访问掩码会被初始化为拥有完全访问权限,标志也会跟随创建内核对象函数的参数而被设置)。然后返回一个句柄(即索引),供外部操作和管理内核对象。

关闭内核对象

无论用什么方式创建内核对象,都要调用CloseHandle函数向系统表明我们已经结束使用对象。

BOOL CloseHandle(Handle hObject);

在函数内部,首先验证传递来的内核对象句柄标识是否是一个“确实有效的一个对象”。如果是,递减内核对象使用计数,如果不是,函数返回FALSE,GetLastError的值为ERROR_INVALID_HANDLE。

未调用CloseHandle来关闭内核对象,会发生对象泄露吗?不一定,因为当进程结束时,操作系统会释放进程所占用的所有资源。但是在进程运行中,可能照成资源泄露。

进程间共享内核对象

使用对象句柄继承:

只有在进程之间有一个父子关系的时候,才可以使用对象句柄。而要继承内核对象句柄需要满足下列条件:

1.
当父进程创建内核对象时,必须向系统指出该内核对象的句柄是可以继承的(即SECURITY_ATTRIBUTE结构的bInheritHandle为TRUE)。当句柄能被继承时,句柄的标志为1。

2. 当父进程创建子进程时,

BOOL WINAPI CreateProcess(

__in          LPCTSTR lpApplicationName,

__in_out      LPTSTR lpCommandLine,

__in          LPSECURITY_ATTRIBUTES lpProcessAttributes,

__in          LPSECURITY_ATTRIBUTES lpThreadAttributes,

__in BOOL bInheritHandles,

__in          DWORD dwCreationFlags,

__in          LPVOID lpEnvironment,

__in          LPCTSTR lpCurrentDirectory,

__in LPSTARTUPINFO lpStartupInfo,

__out LPPROCESS_INFORMATION lpProcessInformation

);

需要向系统表明,创建的子进程可以继承父进程的所有(可继承的)句柄(即bInheritHandles为TRUE)。

满足上述条件后,子进程继承了父进程所有能继承的内核对象句柄了,但是子进程并不知道自己继承了任何句柄,在文档中应指出来。然而子进程并不知道自己句柄表中的内核对象句柄值,这必须由父进程传递句柄值到子进程。可使用方法为:1.通过传递命令行参数 2.可使用进程间通信技术
3.让父进程等待子进程初始化(WaitForInputIdle),然后父进程可以将一条消息发送或发布到由子进程中的一个线程创建的窗口 4.通过父进程向环境块添加一个环境变量。

一些常用函数:

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

修改内核对象句柄的继承标志。

BOOL GetHandleInformation(HANDLE hObject, PDWORD pdwFlags);

获取内核对象句柄标志

为对象命名:

许多内核对象可以命名,当创建内核对象时,大部分的函数的最后一个参数都是PCTSTR pszName,为它传入NULL,表明我们要创建一个未命名的(即匿名)内核对象。不为NULL时,应该是一个字符串地址,这表明内核对象的名称。遗憾的是Microsoft没有提供任何专门的机制来保证内核对象的名称是惟一的。当我们创建一个已指定名称的不同类型的内核对象时,创建内核对象会失败。当创建一个已指定名称的类型相同的内核对象时,如果调用者拥有对该对象的完全安全访问权限,系统会选择调用者的进程句柄表中找一个空白记录项,并初始化为指向现有的内核对象,返回句柄索引值。

由于Microsoft没有提供任何专门保证我们创建独一无二的内核对象名的机制,所以我们最好以一个GUID的字符串形式来保存内核对象名称。利用命名对象可以防止一个应用程序的多个实例运行

当我们利用命名对象来做一个单实例程序时,仍然可能被其他程序先使用了内核对象名称来照成拒绝服务攻击,因为我们的GUID不是随机生成的,而是固定的一个GUID字符串。要防止此种情况,可以使用专用命名空间。要创建一个专用命名空间:

1.
创建边界描述符HANDLE CreateBoundaryDescriptor(PCTSTR pszName, DWORD dwFlags);

2.
创建用户组的一个安全描述符。

BOOL WINAPI CreateWellKnownSid(WELL_KNOWN_SID_TYPE wellKnownSidType, PSID pDoMainSid, PSID pSid, DWORD* cbsid);

3. 将一个特权用户组的SID与边界描述符关联起来。

BOOL AddSIDToBoundaryDescriptor(HANDLE* phBoundaryDescriptor,PSID pRequiredSid);

4. 创建专有命名空间。

HANDLE CreatePrivateNamespace(PSECURITY_ATTRIBUTES psa, PVOID pvBoundaryDescriptor, PCTSTR pszAliasPrefix);

psa可以通过ConvertStringSecurityDescriptorToSecurityDescriptor函数来构造

复制对象句柄:

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

这个函数获得一个进程的句柄表中的一个记录项,然后在另一个进程句柄表中创建这个记录项的一个副本。

使用这个函数可以将进程的内核对象拷贝到另外一个进程的内核对象句柄表中,然后通过通信技术把句柄值传过去。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: