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.
sa.nLength = siezof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE; // Make the returned handle inheritable
HANDLE hMutex = CreateMutex( & sa, FALSE, NULL);
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函数来改变内核对象句柄的继承标志。
HANDLE hObject, // 有效句柄标志
DWORD dwMask, // 想要更改那个或哪些标志
DWORD dwFlags // 希望把标志设为什么
);
#define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002
CloseHandle(hObj); // 会引发异常
HANDLE hObject,
LPDWORD lpdwFlags
);
GetHandleInformation(hObj, & dwFlags);
BOOL fHandleIsInheritable = ( 0 != (dwFlags & HANDLE_FLAG_INHERIT));
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
);
要根据对象名称来共享一个对象,我们必须为此对象指定一个名称。传入一个“以0为终止的名称字符串”的地址。
HANDLE hMutexProcessA = CreateMutex(NULL, FALSE, TEXT( " JeffMutex " ));
// 进程B
HANDLE hMutexProcessB = CreateMutex(NULL, FALSE, TEXT( " JeffMutex " ));
也可以调用Open*函数: