3.1 什么是内核对象
内核对象就是内核中的一块内存,是一个结构,并且只能由内核对象访问,应用程序只能通过调用Windows提供的函数来操作内核对象。每个内核对象都有相同的部分比如安全属性和使用计数器。
3.1.1 内核对象的使用计数
内核对象中的使用计数和进程无关,当进程第一次创建某个内核对象时候使用计数变为1,当另一个进程也调用此内核对象时计数变为2。当进程释放时或者关闭内核对象时(CloseHandle),内核的使用计数减去1,如果使用计数不为0的话,内核不会释放此内核对象。
3.2.2 安全性
内核对象能够得到安全描述符的保护,安全描述符定义了谁能够创建,访问和使用该对象,一般在服务器代码中使用,客户端可以忽略。
所有创建内核对象的函数的参数都有一个指向SECURITY_ATTRIBUTES结构的指针。
typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
只有
lpSecurityDescriptor
成员和安全属性有关。一般此参数传递NULL,表示默认的安全描述。如果需要:
SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = FALSE; HANDLE h = CreateMutex(&sa, FALSE, "XI");
索引 内核对象内存块得指针 访问屏蔽(标志位的DWORD) 标志(标志位的DWORD)
3.2.1 创建内核对象
调用Create&函数族来创建相应的内核对象,返回的是内核对象的句柄(也有个说法就是句柄表的索引),如果创建失败一般会返回0(NULL),也有的会返回INVALID_HANDLE_VALUE=-1,比如CreateFile失败后会返回后者,失败的原因有可能是内存不足或者是安全问题等等。其他对内核操作的函数都需要此句柄值作为参数传递进去,如果传递一个无效的句柄进去,那么GetLastError函数的值将被置为6(ERROR_INVALID_HANDLE)。
3.2.2 关闭内核对象
BOOL CloseHandle(HANDLE hobj);
调用此函数,系统会了清理进程的句柄表中的对应项目,如果使用计数器为0,内核释放该内核对象的资源,如果使用计数器不为0,说明其他进程还在使用此内核对象,则不释放资源。
当进程忘记调用CloseHandle函数,可能造成内存泄露,但是当进程结束的时候资源一样会被释放。
如果传递的参数无效,则函数返回FALSE,并且GetLastError函数的值被设置成ERROR_INVALID_HANDLE。如果是DEBUG阶段,则返回错误信息。
BOOL WINAPI CreateProcess( __in_opt LPCSTR lpApplicationName, __inout_opt LPSTR lpCommandLine, __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes, __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, __in BOOL bInheritHandles, __in DWORD dwCreationFlags, __in_opt LPVOID lpEnvironment, __in_opt LPCSTR lpCurrentDirectory, __in LPSTARTUPINFOA lpStartupInfo, __out LPPROCESS_INFORMATION lpProcessInformation );
注意:子进程再创建其子进程,如果满足上面方式,可以继续继承。如果父进程再创建子进程后,再创建句柄,子进程不会被继承。3.3.2 改变句柄标志
// 设置句柄值可继承: SetHandleInformation(hObject, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); // 设置句柄不可继承: SetHandleInformation(hObject, HANDLE_FLAG_INHERIT, 0); // 设置句柄值不可关闭,受保护: SetHandleInformation(hObject, HANDLE_FLAG_PROJECT_FROM_CLOSE, HANDLE_FLAG_PROJECT_FROM_CLOSE); // 设置句柄值可关闭,不受保护: SetHandleInformation(hObject, HANDLE_FLAG_PROJECT_FROM_CLOSE, 0);
也可以用Open&函数族来打开已经创建的句柄,成功后GetLastError也不会被设置。具体如下
HANDLE Open&(DWORD, BOOL, PCSTR);
第一个参数:表示访问权限。
第二个参数:表示新创建的句柄是否有继承性(注意不是内核对象!)。
第三个参数:不能传递NULL。如果该句柄不存在则返回NULL,GetLastError被设置为2(ERROR_FILE_NOT_FOUND)。
3.3.4 终端服务器的名字空间
Globad,Local,Session程序保留关键字,具体的没弄明白,理解的就是说当服务器的时候,客户端可以访问以这些名字开头的内核对象。
3.3.5 复制对象句柄
BOOL DuplicateHandle( HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE TargetProcessHandle, PHANDLE phTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions);
执行DuplicateHandle函数的进程为ProcessC,原进程为ProcessS,目标进程为ProcessT。则hSourceProcessHandle为进程ProcessS的进程句柄,TargetProcessHandle为进程ProcessT的进程句柄,ProcessC将句柄hSourceHandle从ProcessC拷贝到ProcessT中,值存在phTargetHandle中,dwDesiredAccess新句柄的反问权限,bInheritHandle新句柄的继承性,参数dwOptions有两种类型分别是:
DUPLICATE_SAME_ACCESS忽略参数dwDesiredAccess,新句柄和原进程句柄具有相同的反问权限。
DUPLICATE_CLOSE_SOURCE关闭ProcessS中的拷贝句柄,内核对象的计数不变。
HANDLE hObjProcessS = CreateMutex(NULL, FALSE, NULL); HANDLE hProcessT = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessIdT); HANDLE hObjProcessT; DuplicateHandle(GetCurrentProcess(), hObjProcessS, hProcessT , &hObjProcessT, 0, FALSE, DUPLICATE_SAME_ACCESS); CloseHandle(hObjProcessS); CloseHandle(hProcessT);
注意:
一般DuplicateHandle函数没有在三个进程中使用,因为很难知道原进程的句柄值。
要使用IPC机制通知目标进程,新句柄已经拷贝过去。