使用对象句柄继承
进程之间的继承。
- 父进程指定对象句柄可以继承,把SECURUTY_ATTRIBUTES的bInheritHandle设为TRUE(不想被继承就设为FALSE),使用这个结构创建的对象句柄可以被继承(FALSE就不行)。同时会把进程句柄表里面的相应标志设置(TRUE是0x00000001,FALSE是0x00000000)。
- 父进程创建子进程。
CreateProcess(
LPCTSTR lpApplicationName,//可执行模块名
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributs,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInHeritHandle,//是否继承父进程的句柄表
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartuoInfo,
LPPROCESS_INFORMATION pProcessInformation
);
bInHeritHandle是TRUE,子进程会遍历父进程的句柄表,并把可继承的项复制到子进程句柄表(这个句柄表是新建的空白句柄表)的同一位置,这意味着在父进程和子进程中,对一个内核对象进行标识的句柄值起来完全一样的,并且会递增被继承的内核对象的使用计数。
- 对象句柄的继承只发生在生成子进程的时候,也就是说父进程在创建子进程(被继承)之后,父进程创建的新的可继承的句柄还是不会被之前创建的子进程所继承。
- 改变句柄状态
BOOL SetHandleInformation(
HANDLE hObject,
DWORD dwMask,//要更改的句柄标志
DWORD dwFlags);//把标志设为这个值
BOOL GetHandleInformation(
HANDLE hObject,
PDWORD pdwFlags
);
//打开一个内核对象句柄的继承标志
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); //会引发异常
- 获取句柄状态
BOOL GetHandleInformation(
Handle hObject,
PWORD pdwFlags
);
//检查一个句柄是否可以继承,执行:
DWORD dwFlags;
GetHandleInformation(hObj, dwFlags);
BOOL fHandleIsInheritable = (0 != (dwFlags & HANDLE_FLAG_INHERIT));
- dwMask标志:
1、 HANDLE_FLAG_INHERIT 0x00000001
2、 HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002(告诉系统不允许关闭句柄,一般会用于父进程-子进程-孙进程……)
- 子进程不知道自己继承了任何句柄。到目前为止,为了使子进程得到它想要的一个内核对象的句柄值,最常见的方式是将句柄值作为命令行参数传递给子进程,子进程在初始化时,解析命令行并提取句柄值。
- 注意,句柄继承之所以能够实现,唯一的原因就是“共享的内核对象”的句柄值在父进程和子进程中是完全一样的。
- 其他进程间通信将继承的内核对象句柄值从父进程传入子进程:
1、让父进程等待子进程完成初始化(WaitForInputIdle函数),然后父进程可以将一条消息发送或发布到由子进程中的一个线程创建的一个窗口
2、让父进程向其环境块中添加一个环境变量。变量的名称应该是子进程知道的一个名称,而变量的值应该是准备被子进程继承的那个内核对象的句柄值。然后,当父进程生成子进程的时候,这个子进程会继承父进程的环境变量,通过调用GetEnvironmentVariable来获得继承到内核对象的句柄值。环境变量可以反复继承
为对象命名
- 很多内核对象创建函数的最后一个参数是名称(为NULL表示匿名),以\0结尾的最长MAX_PATH的字符串。
- 这个要特别注意新建的内核对象的名称会不会已经存在了,如果是在同一个进程里,哪怕是它们的内核对象类型不同也是不可以的。
- 如果是进程创建另一个进程中创建的内核对象,如果内核对象的类型相同,并且对它具有完全访问权限,然后这个进程就会新建一个空白项,并初始化指向它。如果类型不匹配或者调用者被拒绝访问,返回失败。
- 进程B成功创建一个已经存在的内核对象时,并不会实际创建一个新的内核对象,而是给进程B分配一个新的句柄值,它标识了内核中的一个现有的互斥量对象。由于在进程B的句柄表中,所以引用计数会递增
- 可以创建命名的内核对象的函数:
HANDLE CreateMutex(
PSECURITY_ATTRIBUTES psa,
BOOL bInitialOwner,
PCTSTR pszName
);
HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa,
BOOL bManualReset,
BOOL bInitialState,
PCTSTR pszName
);
HANDLE CreateSemaphore(
PSECURITY_ATTRIBUTES psa,
LONG lInitialCount,
LONG lMaximumCount,
PCTSTR pszName
);
HANDLE CreateWaitableTimer(
PSECURITY_ATTRIBUTES psa,
BOOL bManualReset,
PCTSTR pszName
);
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD flProtext,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName
);
HANDLE CreateJobObject(
PSECURITY_ATTRIBUTES psa,
PCTSTR pszName
);
通过名称来实现内核对象共享时,进程B调用CreateMutex时,它会向函数传递安全属性信息和第二个参数。如果已经存在一个指定名称的对象,这些参数会被忽略。
-
在调用Create *之后,马上调用GetLastError来判断自己是真正创建了一个新的内核对象还是打开了一个已有的。(已有:ERROR_ALREADY_EXEISTS)
调用Create *和Open *的区别主要在于,如果对象不存在Create *会创建它;而Open *只是简单地以调用失败而告终。
// 03-内核对象.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hMutex = CreateMutex(NULL, FALSE, TEXT("{Test-djskvhbkejwvhkhvhdjhdhjvdbdbhdhbvdhbdh}"));
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
printf("内核对象已存在! {Test-djskvhbkejwvhkhvhdjhdhjvdbdbhdhbvdhbdh}");
CloseHandle(hMutex);
return 0;
}
printf("创建内核对象! {Test-djskvhbkejwvhkhvhdjhdhjvdbdbhdhbvdhbdh}");
getchar();
CloseHandle(hMutex); //Close的时候会关闭句柄,并且使用计数-1,使用为0时销毁对象
return 0;
}
终端服务命名空间
在正在运行终端服务的计算机中,有多个用于内核对象的命名空间。其中一个是全局命名空间,所有客户端都能访问的内核对象放在这个命名空间中,这个命名空间主要是由服务使用。此外,每个客户端会话都有一个自己的命名空间,可以避免会话之间彼此干扰。
- 查看当前进程在哪个Terminal Services会话中运行
#include "stdafx.h"
#include
int _tmain(int argc, _TCHAR* argv[])
{
DWORD dwCurProcessID = GetCurrentProcessId();
DWORD dwSessionID;
if (ProcessIdToSessionId(dwCurProcessID, &dwSessionID))
{
printf("ProcessID:%u, SessionID:%u\n", dwCurProcessID, dwSessionID);
}
else
{
printf("GetSessionID failed!\n");
}
return 0;
}
- 一个服务的命名内核对象始终位于全局命名空间内。默认情况下,在终端服务中,应用程序自己的命名内核对象在会话的命名空间内。也可以强制把一个命名对象放到全局命名空间中,具体做法是:在其名称前面加上“Global\”前缀;显式把命名内核对象放入当前会话的命名孔家,做法:在名称前加“Local\”前缀
- Microsoft认为Global和Local是保留关键字,所以除非是为了强制一个特定的命名空间,否则不应该在对象名称中使用它们
专有命名空间
- 创建内核对象时,可以传递一个指向SECURITY_ATTRIBUTES结构的指针,从而保护对该对象的访问。
- 在Windows Vista发布之前,我们不可能防止一个共享对象的名称被“劫持”。任何进程(即时是最低权限的进程)都能用任何指定的名称来创建一个对象。