Windows内核对象(2) -- 内核对象跨进程访问

网友投稿 798 2022-05-30

虽然内核对象位于独立于进程之外的内核区域,我们在开发中却只能通过调用Win32 API传入HANDLE参数来操作内核对象(如SetEvent等)。然而HANDLE句柄只对当前进程有效,离开了当前进程该句柄就无效了(具体原因参考:Windows内核对象(1) – 内核对象与句柄)。所以说,跨进程访问内核对象的关键在于我们怎么跨进程访问句柄HANDLE?

下面介绍几种方法来实现跨进程共享内核对象。

一、使用句柄继承的方式

只有进程之间有父子关系时,才可以使用句柄继承的方式。在这种情况下,父进程可以生成一个子进程,并允许子进程访问父进程的内核对象。为了使这种继承生效,父进程必须执行几个步骤:

(1). 父进程在创建一个内核对象时,父进程必须向系统指定它希望这个内核对象的句柄是可以继承的。为了创建一个可继承的内核对象,必须分配并初始化一个SECURITY_ATTRIBUTES结构,如:

SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE;  // 可继承的 sa.lpSecurityDescriptor = NULL; HANDLE h = CreateEvent(&sa, TRUE, FALSE, NULL);

(2). 父进程通过CreateProcess生成子进程,且指定bInheritHandles为TRUE,从而允许子进程来继承父进程的那些“可继承的句柄”。

// 启动子进程TestB.exe,将句柄h作为启动参数传给进程TestB // TCHAR cmd_buf[MAX_PATH]; StringCchPrintf(cmd_buf, MAX_PATH, TEXT("TestB.exe %ld"), (long)h); STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; BOOL ret = CreateProcess( NULL,  cmd_buf,  NULL,  NULL,  TRUE,  // 指定子进程可以继承父进程的“可继承句柄” 0,  NULL,  NULL,  &si,  &pi ); CloseHandle(pi.hProcess); CloseHandle(pi.hThread);

由于我们传给bInheritHandles参数的值是TRUE,所以系统在创建子进程时会多做一件事情:它会遍历父进程的句柄表,对它的每一项进行检查,凡是包含一个有效的“可继承的句柄”的项,都会将该项完整的复制到子进程的句柄表。在子进程的句柄表中,复制项的位置与它在父进程句柄表中的位置完全一样(包含索引),这个就意味着:在父进程和子进程中,对一个内核对象进行标识的句柄值也是完全一样的。所以我们只需要通过某种方式(如上面示例中的启动参数的方式,或者环境变量的方式等任何进程间通讯的方式)将这个值告诉子进程,子进程就可以将该值转成HANDLE,然后使用这个HANDLE来调用系统API。

二、使用DuplicateHandle方式

明白DuplicateHandle的工作原理,需要先了解进程句柄表,可以参考Windows内核对象(1) – 内核对象与句柄)

2.1 DuplicateHandle功能

DuplicateHandle函数可以将指定“源进程的句柄表”中的某一项复制到“目的进程句柄表”中(除了索引),并且返回该项在目的进程句柄表中的索引(即HADNLE)。

可以在任何时候调用DuplicateHandle函数,DuplicateHandle对源句柄是否是可继承的没有要求。

函数声明如下:

BOOL DuplicateHandle(   HANDLE hSourceProcessHandle,   HANDLE hSourceHandle,   HANDLE hTargetProcessHandle,   LPHANDLE lpTargetHandle,   DWORD dwDesiredAccess,   BOOL bInheritHandle,   DWORD dwOptions );

DuplicateHandle详细介绍可以参考MSDN:https://msdn.microsoft.com/en-us/library/windows/desktop/ms724251(v=vs.85).aspx

2.2 支持的句柄类型

DuplicateHandle函数不能复制所有类型的句柄,只能复制如下类型的句柄(从MSDN复制而来):

不同的事件类型对应的dwDesiredAccess参数不同,具体参考MSDN。

2.3 使用示例

进程TestA源码

int main(int argc, char** argv) { HANDLE h = CreateEvent(NULL, TRUE, FALSE, NULL); // 启动子进程TestB.exe // TCHAR cmd_buf[MAX_PATH]; StringCchPrintf(cmd_buf, MAX_PATH, TEXT("D:\TestB.exe"), (long)h); STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; BOOL ret = CreateProcess(NULL, cmd_buf, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); assert(ret); assert(pi.hProcess); HANDLE duplicated_h = NULL; ret = DuplicateHandle(GetCurrentProcess(), h, pi.hProcess, &duplicated_h, 0, FALSE, DUPLICATE_SAME_ACCESS); WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); bool has_signal = WaitForSingleObject(h, 0) == WAIT_OBJECT_0; assert(has_signal == true); return 0; }

子进程TestB源码

int main(int argc, char** argv) { long l = 0; printf("Input Handle:"); scanf("%ld", &l); HANDLE h = (HANDLE)l; bool has_signal = WaitForSingleObject(h, 0) == WAIT_OBJECT_0; assert(has_signal == false); SetEvent(h);     return 0; }

在父进程TestA中创建一个不可继承的事件 -> 然后启动子进程TestB -> 调用DuplicateHandle复制句柄项到TestB进程句柄表 -> 并向TestB输入句柄值 -> TestB访问该事件句柄,将事件置为有信号状态。

三、使用命名的内核对象的方式

3.1 实现原理

这种方式严格的说已经不是文章开头说到的跨进程访问句柄了,有点类似跨进程直接访问内核对象了。

该方式实现起来比较简单,就是在调用创建内核对象的Create***函数时,通过pszName参数为内核对象取一个名字。

如创建事件Event的函数CreateEvent:

HANDLE WINAPI CreateEvent(   LPSECURITY_ATTRIBUTES lpEventAttributes,   BOOL bManualReset,   BOOL bInitialState,   LPCTSTR lpName  // 指定名称 );

HANDLE h = CreateEvent(NULL, TRUE, FALSE, TEXT("TestA_Obj"));

若在其他进程中要访问这个内核对象,只需要使用打开函数Open***打开该内核对象,系统就会在进程的句柄表中插入一条记录,并返回这条记录的索引,也就是句柄。需要注意的是,在打开内核对象时需要留意返回值和GetLastError函数的返回值。由于内核对象是有访问权限的,有时候虽然这个名字的内核对象存在,但该进程却不见得有权限可以打开它,这个时候GetLastError函数会返回失败的原因。

以打开事件的函数OpenEvent为例:

HANDLE h = OpenEvent(READ_CONTROL, FALSE, TEXT("TestA_Obj")); if (h == NULL) { if (GetLastError() == ERROR_ACCESS_DENIED) { // 没有READ_CONTROL权限 } }

3.2 全局命令空间

不同的会话(Session)有不同的内核对象命名空间(如windows服务程序位于Session 0,而普通的用户进程位于Session 1),要通过名称访问其他会话中的内核对象,需要在名称前面加上Session\<当前会话ID>。Windows提供了一个全局的内核对象命名空间,处于任何会话中的进程都可以访问该命名空间,将内核对象放入全局命令空间的方式很简单:只需要在内核对象名称前加入Global\即可。

如:

HANDLE h = CreateEvent(NULL, TRUE, FALSE, TEXT("Global\TestA_Obj"));

Windows内核对象(2) -- 内核对象跨进程访问

windows

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:少即是多 - 组织简化的7个设计原则
下一篇:深度学习 GPU环境 Ubuntu 16.04 + Nvidia GTX 1080 + Python 3.6 + CUDA 9.
相关文章