1、何为内核对象
每个内核对象都只是一个内存块,它由操作系统内核分配,并只能由操作系统内核访问。这个内存块是一个数据结构,其成员维护着于对象相关的信息。少数成员(安全描述符何使用计数等)是所有对象都有的,但其他大多数成员都是不同类型的对象特有的。
e.g. 访问令牌(access token)对象、事件对象、文件对象、文件映射对象、I/O完成端口对象、作业对象、邮件槽(mailsolt)对象、互斥量(mutex)对象、管道(pipe)对象等
*可利用Sysintenals的WinObj(http://technet.microsoft.com/zh-cn/sysinternals/bb896657.aspx)工具查看内核对象类型的列表。
1.1、使用计数
使用计数事所有内核对象类型都有的一个数据成员。初次创建对象事,其使用计数被设为1。如果另一进程获得对现有内核对象的访问后,使用计数会递增。进程终止运行后,操作系统内核将自动递减进程仍然打开的所有内核对象的使用计数。一旦对象的使用计数变为0,操作系统内核就会销毁该对象。
1.2、内核对象的安全性
内核对象可以用一个安全描述(security descriptor, SD)符来保护。它描述了谁拥有对象;哪些组和用户被允许访问或使用此对象;哪些组何用户被拒绝访问此对象。
用于创建内核对象的所有函数几乎都有指向一个SECURITY_ATTRIBUTES结构的指针
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength; //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
LPVOID nLength; //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
LPVOID nLength; //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
LPVOID nLength; //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
LPVOID nLength; //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
LPVOID nLength; //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
LPVOID nLength; //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
LPVOID nLength; //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
LPVOID lpSecurityDescriptor; //和安全性有关的成员
BOOL lpSecurityDescriptor; //和安全性有关的成员
BOOL lpSecurityDescriptor; //和安全性有关的成员
BOOL lpSecurityDescriptor; //和安全性有关的成员
BOOL lpSecurityDescriptor; //和安全性有关的成员
BOOL lpSecurityDescriptor; //和安全性有关的成员
BOOL lpSecurityDescriptor; //和安全性有关的成员
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;
2、进程内核对象句柄表
进程的句柄表结构
索引
|
指向内核对象内存块的指针
|
访问掩码(包含标志位的一个DWORD)
|
标志
|
1
|
0x?????????
|
0x?????????
|
0x?????????
|
2
|
0x?????????
|
0x?????????
|
0x?????????
|
…
|
…
|
…
|
…
|
2.1、创建一个内核对象
用于创建内核对象的任何函数都会返回一个与进程相关的句柄,这个句柄可由同一个进程中运行的所有线程使用。由于句柄值实际事作为进程句柄表的索引来使用的,索引这些句柄是与当前这个进程相关的,无法供其他进程使用。
调用函数来创建一个内核对象时,如果调用失败,多数返回的句柄值是0(NULL),但有几个函数会返回-1(也就是在Winbase.h中定义的INVALID_HANDLE_VALUE)。检查它们的返回值是,务必相当仔细。
2.2、关闭内核对象
无论以什么方式创建内核对象,我们都要调用CloseHandle向系统表明我们已经结束使用对象。
BOOL CloseHandle(
HANDLE hObject // handle to object
);
在内部,该函数首先检查主调进程的句柄表,验证“传给函数的句柄值”标识的是“进程确实有权访问的一个对象”。
1)如果句柄是有效的,系统就将获得内核对象的数据结构的地址,并将结构中的“使用计数”成员递减,如果使用计数变成0,内核对象将被销毁,并从内存中去除。
2)如果句柄是无效的,
如果进程是正常运行的,CloseHandle将返回FALSE,而GetLastError返回ERROR_INVALID_HANDLE。
如果进程正在被调试,那么系统将抛出0xC0000008异常(“指定了无效的句柄”)。
hObject // handle to object
);
在内部,该函数首先检查主调进程的句柄表,验证“传给函数的句柄值”标识的是“进程确实有权访问的一个对象”。
1)如果句柄是有效的,系统就将获得内核对象的数据结构的地址,并将结构中的“使用计数”成员递减,如果使用计数变成0,内核对象将被销毁,并从内存中去除。
2)如果句柄是无效的,
如果进程是正常运行的,CloseHandle将返回FALSE,而GetLastError返回ERROR_INVALID_HANDLE。
如果进程正在被调试,那么系统将抛出0xC0000008异常(“指定了无效的句柄”)。
*检测内核对象泄露:
用Windows任务管理器,选择(查看)->(选择列),然后选择显示(句柄数)。便可在进程出监视任何一个应用程序的内核对象数了。
使用Sysinternals提供的Process Explorer工具(http://technet.microsoft.com/zh-cn/sysinternals/bb896653.aspx),选择(View)->(Select Columns),选择(Handle),选中所有列标题。 在顶部选择想要检查的进程,按F5来获得一份最新的内核对象列表。 然后启动应用程序并开始指向一个待查的工作流。完成之后,再次按F5。在此期间生成的每个内核对象都显示为绿色。
3、跨进程边界共享内核对象
内核对象的句柄是与每一个进程相关的,是为确保健壮性(可靠性)和安全性。
3.1、使用对象句柄继承
只有在进程之间有一个父-子关系的时候,才可以使用对象句柄继承。
首先,父进程必须向系统指出它希望这个对象的句柄是可继承的。
e.g.
SECURITY_ATTRIBUTES sa;
sa.nLength = siezof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE; //Make the returned handle inheritable
HANDLE hMutex = CreateMutex(&sa, FALSE, NULL);
然后,由父进程生成子进程,通过
CreateProcess函数完成
BOOL CreateProcess(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles, //通常情况下设为FALSE(表明我们不希望子进程继承父进程句柄表中的“可继承句柄”)
//如果传递TRUE,子进程就会继承父进程的“可继承句柄”的值。
//(系统会遍历符进程的句柄表,对它的每一个记录项进行检查,凡是包含一个
//有效的“可继承句柄”的项,都会被完整地复制到子进程的句柄表)
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
除了复制句柄表的记录项,系统还会递增内核对象的使用计数,引擎两个进程现在都在使用这个对象。
3.2改变句柄的标志
1)父进程想控制哪些子进程能继承内核对象句柄。可以调用
SetHandleInformation函数来改变内核对象句柄的继承标志。
BOOL SetHandleInformation(
HANDLE hObject, //有效句柄标志
DWORD dwMask, //想要更改那个或哪些标志
DWORD dwFlags //希望把标志设为什么
);
每个句柄都关联了两个标志:
#define HANDLE_FLAG_INHERIT 0x00000001
#define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002
如果想打开一个内核对象句柄的继承标志
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
如果想关闭这个标志
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, 0);
如果告诉系统不允许关闭句柄
SetHandleInformation(hObj,,HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);
CloseHandle(hObj); //会引发异常
2)使用
GetHandleInformation函数返回句柄标志
BOOL GetHandleInformation(
HANDLE hObject,
LPDWORD lpdwFlags
);
e.g.检查句柄是否可继承
DWORD dwFlags;
GetHandleInformation(hObj, &dwFlags);
BOOL fHandleIsInheritable = (0 != (dwFlags & HANDLE_FLAG_INHERIT));
3.3为对象命名
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
所有这些函数的最后一个参数都是pszName。传入NULL,相当于向系统表明我们要创建一个未命名的(即匿名)内核对象。
要根据对象名称来共享一个对象,我们必须为此对象指定一个名称。传入一个“以0为终止的名称字符串”的地址。
//进程A
HANDLE hMutexProcessA = CreateMutex(NULL, FALSE, TEXT("JeffMutex"));
//进程B
HANDLE hMutexProcessB = CreateMutex(NULL, FALSE, TEXT("JeffMutex"));
当进程B调用CreateMutex时,系统会查看是否存在一个名为 "JeffMutex "("JeffMutex "的对象是一个互斥量对象)的内核对象。如果存在,接着检查对象的类型,和调用者是否拥有该对象的完全访问权限。 如果答案是肯定的,系统就会在进程B的句柄表中查找一个空白记录项,并将其初始化为指向现有的内核对象;否则,返回NULL(失败)。
也可以调用Open*函数:
posted on 2009-11-26 11:06
longshen 阅读(947)
评论(0) 编辑 收藏 引用 所属分类:
VC++