3.3 跨进程边界共享内核对象
3.3.1 使用对象句柄继承
(1)对象句柄继承,只发生在进程之间有父子关系的时候(即一个进程由另一个进程CreateProcess出来)
(2)内核对象句柄继承的实现
①父进程必须先指出哪些内核对象句柄是可继承(注意不是内核对象本身的继承,而是内核对象的句柄继承),父进程在创建内核对象时要将SECURITY_ATTRIBUTES的bInheritHandle字段设为TRUE,表示可继承。这时句柄表中相应的记录项的标志位被设为1,否则为0。
②父进程调用CreateProcess创建子进程,其中的bInheritHandles设为TRUE,表示希望子进程继承父进程句柄表的中“可继承句柄”。这时新创建的子进程不会立即执行,而是先遍历父进程的句柄表,并将每一个有效的“可继承句柄”完整地复制到子进程的句柄表,并且复制项的位置与父进程的位置完全一致。(即两句柄的索引值一样,如索引值为3,则子进程句柄表中也有3个记录项,其中第3个就是父进程可继承句柄,其余两个被标记为不可用,见课本44表3-3),并递增内核对象的使用计数。
③如果子进程再调用CreateProcess生成自己的子进程(父进程的孙进程),并且bInheritHandles也设为TRUE,则孙进程也会继承这个内核对象,并在孙进程的句柄表中,继承的对象句柄具有相同的句柄值、访问掩码及标志。
④为了让子进程得到这个内核对象的句柄值,可通过命令行参数或通过向父进程环境块添加一个环境变量(环境变量也是会被继承的)或其他进程间通信技述,将句柄值从父进程传递给子进程。
⑤为了销毁内核对象,父子乃至孙进程都要调用CloseHandle(因为他们使用的是相同的内核对象),只有都CloseHandle,使用计数值才会减为0。
(3)对象句柄的继承只发生在生成子进程的时候,如果父进程后来又创建了一个可继承的内核对象,正在运行的子进程是不会继承这个新的内核对象句柄的。
(4)改变句柄的标志
①使用场景,如父进程的可继承句柄只希望被多个子进程中的其中一个继承。即改变了这句柄标志,只影响之后创建的子进程。
②SetHandleInformation函数——改变句柄的标志
参数 |
描述 |
HANDLE hObject |
在本进程中要改变标志的内核对象句柄,会影响该进程的子进程 |
DWORD dwMask |
想更改哪个或者哪些标志位 HANDLE_FLAG_INHERIT(1)——是否被继承标志 HANDLE_FLAG_PROTECT_FROM_CLOSE(2)——允许或禁止关闭句柄 |
DWORD dwFlags |
将上述的标志位设成什么样的值 |
★改变内核对象句柄可继承标志
打开继承: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_CLOS)
允许关闭:SetHandleInformation(hObj, HANDLE_FLAG_PROTECT_FROM_CLOSE,0)
这时如果任何一个进程(含父子进程)调用CloseHandle都会引发异常。但要注意在子进程里面如果又将该内核对象的该标志更改为允许关闭的,那该对象照样会被关闭。
③获取内核对象有关的标志信息GetHandleInformation,如获取是否可以继承
DWORD dwFlags;
GetHandleInformation(hObj,&dwFlags);
BOOL fHandleIsInheritable =(0!=(dwFlags & HANDLE_FLAG_INHERIT));
3.3.2 为对象命名
当以不同的用户名同时登录计算机,会产生不用的Session来服务这些用户。其中Session0是随系统启动自动创建的会话,用于提供一些系统服务(如打印机等服务进程),以后登录的用户,会分别产生Session1、Session2……等等。
命名空间 |
描述 |
备注 |
全局空间 |
创建的内核对象,能同时被Session1、Session2、……、SessionN等所有会话中的程序同时访问到的对象。 对象的命名形式:Global\MyObjectName |
①对象名称前面的Global、Local或MyNameSpace表示出对象的命名空间,也即指定了可见范围。 ②私有空间只有指定的用户能访问! |
本地空间 |
本地空间:只能被当前Session里的应用程序访问到的内核对象 命名形式:Local\MyObjectName或MyObjectName |
|
私有空间 |
自定义的命名空间如:最终MyNameSpace\MyObjectName 其中的“MyNameSpace”是我们定义的命名空间的别名,其他应用程序根本不知道实际的空间名字,因此可以起到保护内核对象的效果。 |
★私有空间默认是全局可见的(这有点奇怪),但可在名称前面再加上Global和Local来指定可见范围。如
CreatePrivateNameSpace(&sa,&h_gBoundary,L"aliasName");//创建私有空间,别名为aliasName
//在私有空间aliasName中,创建名为MyMutex的互斥对象,可以在"Local\\aliasName",改为本地空间里的一个私有空间,这个空间下创建的对象将是本地空间里且指定用户才能访问。
StringCchPrint(szMutex,_countof(szMutex),TEXT("%s\\%s"),L"aliasName","MyMutex");
HANDLE hMutex = CreateMetux(NULL,FALSE,szMutex);
3.3.2.1 创建一般的命名对象
(1)创建内核对象的函数一般最后一个参数是pszName,当为NULL表示创建匿名内核对象。否则创建一个以pszName参数命名的对象(但Windows内部并没有办法保证该名称是唯一的,即使内核对象的类型不同也不行),如果创建同名对象,会返回NULL,用GetLastError得到ERROR_INVALID_HANDLE,但这错误代码说明不了什么问题。
(2)利用对象命名来共享内核对象时,两个进程不一定是父子关系、内核对象句柄也可以不是可继承的。
①以创建互斥量内核对象为例来分析
进程A:HANDLE hMutexProcessA = CreateMutex(NULL,FALSE,TEXT("JeffMutex"));
进程B:HANDLE hMutexProcessB = CreateMutex(NULL,FALSE,TEXT("JeffMutex"));
②系统首先检查"JeffMutex"内核对象是否存在,然后检查对象类型是否相同,接着进行访问权限的检查。如果都通过,就在进程B的句柄表中找到一个空白记录项,让其指向现有的内核对象,这时进程B得到的对象与进程A实际上是同一个对象,实际上的真正的创建,只是将使用计数增加1。(注意两个对象的句柄值可能不一样,这与句柄继承机制是不同的!)
③进程B创建内核对象时,如果指定名称的对象确实存在,则其CreateMutex函数中的安全属性信息和第2个参数将被忽略。
④要判断是新创建一个内核对象还是打开一个现有的,可在CreateMutex之后,调用GetLastError函数,当错误代码为ERROR_ALREADY_EXISTS时,表示打开一个现有的。
⑤为了实现内核对象的共享,也可以考虑用Open*函数,找到指定名称的对象时,会在自己所在的进程句柄表中增加一个相应的记录项,并使该对象的使用计数递增。该函数与Create*主要区别在于,如果对象不存在,Create*会创建它,而Open*不会,只是简单地以调用失败而告终。还有一个不同,Open*还可以指定打开的内核对象是否可继承。
⑥因Windows内部没办法保证名称的唯一性,建议用GUID来作对象的名称,以达到对象名称不重复。(方法是VS→工具→创建GUID)
【Singleton1程序】利用互斥量对象实现
#include <stdio.h>
#include <windows.h>
int main()
{
//利用GUID生成唯一的锁名,防止内核对象重名
HANDLE h = CreateMutex(NULL, FALSE,
TEXT("{349210D3-EF54-4EC9-8313-9F47435D785D}"));
if (GetLastError()== ERROR_ALREADY_EXISTS)
{
CloseHandle(h);
return 0;
}
printf("单例程序正在运行中...\n");
//为了演示,这里暂停一下cmd输出窗口
system("pause");
CloseHandle(h);
return 0;
}
3.3.2.2 终端服务命名空间
(1)终端服务(Terminal Services),其工作原理是客户机和服务器通过TCP/IP协议和标准的局域网构架联系。通过客户端终端,客户机的鼠标、键盘的输入传递到终端服务器上,再把服务器上的显示传递回客户端。客户端不需要具有计算能力,至多只需提供一定的缓存能力。众多的客户端可以同时登录到服务器上,仿佛同时在服务器上工作一样,它们之间作为不同的会话连接是互相独立的。此外,远程桌面(Remote Desktop)和快速用户切换也是利用终端服务会话来实现的。(快速用户切换的方法:按Win+L或当登录两用户后,在任务管理器→用户→选择相应的用户,然后右键,连接)
(2)全局命名空间和会话私有的命名空间
①全局命名空间的内核对象供所有客户端会话共享,在全局空间中创建内核对象要在名称前加“Global\”为前缀,如
HANDLE h = CreateEvent(NULL,FALSE,FALSE,TEXT("Global\\JeffEvent"))
②会话私有的命名空间(默认),名称前加“Local\”前缀或省略前缀
HANDLE h = CreateEvent(NULL,FALSE,FALSE,TEXT("Local\\JeffEvent"))
HANDLE h = CreateEvent(NULL,FALSE,FALSE,TEXT("JeffEvent"))//同上
【TerminalService】终端服务命名空间中内核对象的测试程序
保持帐号A的程序仍在运行中,再登录Windows帐号B,运行本程序两个实例,测试结果
/*-----------------------------------------------------------------------------------
TerminalService程序需要用登录不同的Windows帐号同时运行,才能看出效果!
建议程序测试流程:
1、先登录Windows帐号A,运行两个本程序实例
2、保持上述两个实例仍在运行中,再登录Windows帐号B,再运行两个实例
-----------------------------------------------------------------------------------*/
#include <stdio.h>
#include <windows.h>
int main()
{
//先显示进程ID号和所在的会话ID
DWORD processID = GetCurrentProcessId();
DWORD sessionID;
if (ProcessIdToSessionId(processID, &sessionID))
{
wprintf(TEXT("Process '%u' runs in Terminal Services session '%u'\n"),
processID, sessionID);
//测试,尝试在全局命名空间中创建内核对象,实验在不同帐户下同时运行该程序时,
//第2个启动的程序会提示内核对象己存在的错误
HANDLE hGlobalMutex = CreateMutex(NULL, FALSE, TEXT("Global\\MyMutex"));
if (hGlobalMutex == NULL || ERROR_ALREADY_EXISTS == GetLastError())
printf("错误提示:全局命名空间己经存在名称为“MyMutex”的内核对象!\n");
else
printf("在全局命名空间中成功创建名称为“MyMutex”内核对象!\n");
//在局部命名空间中创建内核对象,在不同帐户下同时运行该程序,可以创建同名的内核对象
HANDLE hLocalMutex = CreateMutex(NULL, FALSE, TEXT("Local\\MyMutex"));
//HANDLE hLocalMutex = CreateMutex(NULL, FALSE, TEXT("MyLocalMutex")); //同上
if (hLocalMutex == NULL || ERROR_ALREADY_EXISTS == GetLastError())
printf("错误提示:会话(SessionID=%u)命名空间中己存在名称为“MyMutex”同名内核对象!\n",sessionID);
else
printf("在会话(sessionID=%u)的命名空间中成功创建名称为“MyMutex”的内核对象!\n",sessionID);
system("pause"); //这里必须暂时,否则当CloseHandle后内核对象会被释放
CloseHandle(hGlobalMutex);
CloseHandle(hLocalMutex);
}
else
{
wprintf(TEXT("Unable to get Terminal Service session ID for process:'%u'\n"), processID);
}
system("pause");
return 0;
}
3.3.2.3 专有命名空间
(1)DoS攻击:如果恶意程序先于Singleton1程序建立同名的互斥量对象,该程序无法启动,很容易被劫持,会错误地以为它自己的另一个实例正在运行,这就是DoS攻击机制。显然未命名的内核对象不会遭受DoS攻击。
(2)利用边界描述符创建私有命名空间自身名称加以保护,从而达到保护内核对象的目的。
专有命名空间类似于在内核对象的名称之前加一个目录名称,但这个命名空间没有父目录,也没有名称,所以显示出来的前缀为"\..\锁名 ",从而不会暴露专有命名空间的名称,即减少名称冲突,又可免遭劫持能更好的防范名称被劫持。
(3)创建专有命名空间
①创建边界描述符CreateBoundaryDescriptor:用于定义那些在命名空间中要被隔离的对象的边界。
参数 |
描述 |
LPCTSTR Name |
边界描述符的名称 |
ULONG Flags |
这个参数是为以后保留的。目前没什么用,可以为之传入0 |
返回值 |
注意这里的返回值虽然是HANDLE类型的,但并不是一个内核对象句柄,而是指针,指向一个用户模式的结构,该结构包含了边界的定义。删除里用DeleteBoundaryDescriptor。 |
②将边界描述符与本地管理员组的安全描述符关联起来
A、创建一个SID(安全描述符):CreateWellKnownSid
参数 |
描述 |
WELL_KNOWN_SID_TYPE WellKnownSidType |
SID类型 WinBuiltinAdministratorsSid:表示管理员账户组 WinWorldSid:表示所有账户 |
PSID DomainSid |
指向创建了SID的域的指针,为NULL时表示使用本地计算机 |
PSID pSid, |
指向要返回的SID的存储地址,为传出的参数 |
DWORD *cbSid |
指向存储pSid的大小的地址 |
B、将SID与边界描述符关联起来AddSIDToBoundaryDescriptor,用来决定谁能进入边界并创建命名空间。
参数 |
描述 |
HANDLE *BoundaryDescriptor |
创建边界描述符返回的句柄 |
PSID RequiredSid |
指向SID结构体的指针 |
③创建或打开专有命名空间的名称
A、创建专有命名空间CreatePrivateNamespace
参数 |
描述 |
SECURITY_ATTRIBUTES* sa |
传给Windows使用,用于允许或禁止应用程序通过OpenPrivateNamespace访问专有命名空间来打开或创建内核对象。为谁能“打开命名空间”设置一个筛选层。 |
LPVOID lpBoundaryDescriptor |
指向边界描述符的指针 |
LPCTSTR lpAliasPrefix |
用于创建内核对象的字符串前缀的别名 |
★将一个字符串格式 安全描述符 转换为一个有效的、 功能的安全描述符
ConvertStringSecurityDescriptorToSecurityDescriptor
参数 |
描述 |
LPCTSTR StringSecurityDescriptor |
指向一个空结尾的字符串包含要转换的字符串格式安全描述符的指针 |
DWORD StringSDRevision |
指定 StringSecurityDescriptor 字符串的修订级别。 当前,此值必须 SDDL_REVISION_1 |
PSECURITY_DESCRIPTOR SecurityDescriptor |
指向一个变量,接收转换后的安全描述符的指针的指针。要释放返回的缓冲区,调用 LocalFree 函数 |
PULONG SecurityDescriptorSize |
指向一个变量,接收以字节为单位的转换后的安全描述符的指针的大小。此参数可以是NULL |
返回值 |
如果该函数成功,返回值是,则返回非零值。 如果函数失败,返回值是零。 若要获取扩展的错误的信息,请调用 GetLastError 。 GetLastError 可能会返回以下错误代码之一。 ERROR_INVALID_PARAMETER:参数不是有效的 ERROR_UNKNOWN_REVISION:SDDL 修订级别无效 ERROR_NONE_MAPPED:一个 安全标识符 (SID 输入的安全描述符字符串中) 找不到一个帐户查找操作 |
★打开己有的专有命名空间:OpenPrivateNamespace
★关闭专有命名空间:ClosePrivateNamespace
④利用专有命名空间名称创建内核对象
【Singleton2程序】
/*****************************************************************************************
Module:Singleton.cpp
Notices:Copyright(c) 2008 Jeffery Richter & Christophe Nasarre
*****************************************************************************************/
#include "..\..\CommonFiles\CmnHdr.h"
#include <tchar.h>
#include <strsafe.h>
#include <sddl.h>
#include "resource.h"
/////////////////////////////////////////////////////////////////////////////////////////
//主对话框
HWND g_hDlg;
//互斥对象、边界描述符和命名空间——用来检测前一个实例是否正在运行
HANDLE g_hSingleton = NULL;
HANDLE g_hBoundary = NULL;
HANDLE g_hNameSpace = NULL;
//跟踪命令空间是否被创建或被打开
BOOL g_bNamesapceOpened = FALSE;
//专有命名空间和边界描述符的名称
PCTSTR g_szBoundary = TEXT("3-Boundary");
PCTSTR g_szNameSpace = TEXT("3-Namespace");
#define DETAILS_CTRL GetDlgItem(g_hDlg,IDC_EDIT_DETAILS)
/////////////////////////////////////////////////////////////////////////////////////////
//增加字符串到编辑框控件中
/* VA_LIST的用法:
*(1)首先在函数里定义一个VA_LIST型的变量,这个变量是指向参数的指针;
*(2)然后用VA_START宏初始化变量刚定义的VA_LIST变量;
*(3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的
* 类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);
*(4)最后用VA_END宏结束可变参数的获取。 */
void AddText(PCTSTR pszFormat, ...)
{
va_list argList;
va_start(argList, pszFormat);
TCHAR sz[20 * 1024];
Edit_GetText(DETAILS_CTRL, sz, _countof(sz));
//strchr函数原型:extern char *strchr(const char *s,char c);查找字符串s中首次出现字符c的位置
//strchr在字符串str中查找字符ch第一次出现的位置,找到后返回一个指向该位置的指针。如果该字符不
//存在于字符串中,则返回一个NULL指针
//本例先找到编辑框字符串最后的位置,用strchr函数查找,然后用_vstprintfs_s格式化缓冲区
_vstprintf_s(_tcschr(sz, TEXT('\0')), _countof(sz) - _tcslen(sz), pszFormat, argList);
Edit_SetText(DETAILS_CTRL, sz);
va_end(argList);
}
/////////////////////////////////////////////////////////////////////////////////////////
void CheckInstance()
{
//创建边界描述符
//参数Name:边界描述符名称;Flags:保留字,没用,填0
//返回值:虽为句柄,但实际上是个指针,不能CloseHandle,要用DeleteBoundaryDescriptor删除
g_hBoundary = CreateBoundaryDescriptor(g_szBoundary, 0);
//创建与本地管理员组相关的用户SID
BYTE LocalAdminSID[SECURITY_MAX_SID_SIZE];
PSID pLocalAdminSID = &LocalAdminSID;
DWORD cbSID = sizeof(LocalAdminSID);
//CreateWellKnownSid——创建与本地管理员组相关的用户SID
//第1个参数: WinBuiltinAdministratorsSid表示管理员账户组,WinWorldSid:表示所有账户
//第2个参数:指向创建了SID的域的指针,为NULL时表示使用本地计算机
//第3个参数:指向要返回的SID的存储地址,为传出的参数
//第4个参数:指向要返回的SID的存储地址,为传出的参数
if (!CreateWellKnownSid(
WinBuiltinAdministratorsSid,NULL,pLocalAdminSID,&cbSID ))
{
AddText(TEXT("添加安全描述符到边界描述符失败:%u\r\n"), GetLastError());
return;
}
//将SID与边界描述符关联起来AddSIDToBoundaryDescriptor,用来决定谁能进入边界并创建命名空间
//只有管理员身份运行的应用程序能获得该命名空间中的内核对象
if (!AddSIDToBoundaryDescriptor(&g_hBoundary, pLocalAdminSID)){
AddText(TEXT("添加安全描述符到边界描述符失败:%u\r\n"), GetLastError());
return;
}
//创建本地管理员专有的命名空间
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.bInheritHandle = FALSE;
//以下这个函数在sddl.h文件中,用来将一个字符串格式 安全描述符 转换为一个有效的、 功能
//的安全描述符。
//第1个参数:指向一个空结尾的字符串包含要转换的字符串格式安全描述符的指针
// "D:"表示DACL,
// (A;;GA;;;BA)表示允许完全控制管理员帐号(Allow full control to Adminstrators)
/*
// Define the SDDL for the DACL. This example sets
// the following access:
// Built-in guests are denied all access.
// Anonymous logon is denied all access.
// Authenticated users are allowed read/write/execute access.
// Administrators are allowed full control.
// Modify these values as needed to generate the proper
// DACL for your application.
TCHAR * szSD = TEXT("D:") // Discretionary ACL
TEXT("(D;OICI;GA;;BG)") // Deny access to built-in guests
TEXT("(D;OICI;GA;;;AN)") // Deny access to anonymous logon
TEXT("(A;OICI;GRGWGX;;;AU)") // Allow read/write/execute to authenticated users
TEXT("(A;OICI;GA;;;BA)"); // Allow full control to administrators
*/
//第2个参数:指定 StringSecurityDescriptor 字符串的修订级别。 当前,此值必须 SDDL_REVISION_1
//第3个参数:指向一个变量,接收转换后的安全描述符的指针的指针。要释放返回的缓冲区,调用 LocalFree 函数
//第4个参数:指向一个变量,接收该的大小以字节为单位的转换后的安全描述符的指针。此参数可以是NULL
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(
TEXT("D:(A;;GA;;;BA)"),SDDL_REVISION_1,&sa.lpSecurityDescriptor,NULL))
{
AddText(TEXT("安全描述符创建失败:%u\r\n"), GetLastError());
return;
}
g_hNameSpace = CreatePrivateNamespace(&sa, g_hBoundary, g_szNameSpace);
LocalFree(sa.lpSecurityDescriptor);
//检查专有命名空间创建是否成功
DWORD dwLastError = GetLastError();
//创建失败
if (g_hNameSpace == NULL)
{
//如果被拒绝,则直接返回,这段代码必须在本地管理员账户下运行
if (dwLastError == ERROR_ACCESS_DENIED){
AddText(TEXT("创建命名空间时被拒绝。\r\n"));
AddText(TEXT(" 必须以管理员身份运行。\r\n\r\n"));
return;
}else{
//如果另一个实例己经创建了该命名空间,就打开它
if (dwLastError == ERROR_ALREADY_EXISTS)
{
AddText(TEXT("创建专有命名空间失败:%u\r\n"), dwLastError);
g_hNameSpace = OpenPrivateNamespace(g_hBoundary, g_szNameSpace);
if (g_hNameSpace == NULL){
AddText(TEXT(" 并且打开命名空间失败:%u\r\n"), GetLastError());
return;
}else{
g_bNamesapceOpened = TRUE;
AddText(TEXT(" 但是打开命名空间成功。\r\n\r\n"));
}
}else{
AddText(TEXT("发生了未知错误:%u\r\n\r\n"),dwLastError);
return;
}
}
}
//尝试创建一个基于命名空间的具名互斥对象
TCHAR szMutexName[64];
StringCchPrintf(szMutexName, _countof(szMutexName), TEXT("%s\\%s"),
g_szNameSpace, TEXT("Singleton"));
g_hSingleton = CreateMutex(NULL, FALSE, szMutexName);
if (GetLastError()==ERROR_ALREADY_EXISTS)
{
//己经存在另一个实例对象
AddText(TEXT("单一实例应用程序的另一个实例正在运行:\r\n"));
AddText(TEXT("-->不能访问应用程序功能。\r\n"));
}
else
{
AddText(TEXT("单一实例应用程序的第一个实例:\r\n"));
AddText(TEXT("-->现在访问应用程序功能。\r\n"));
}
}
/////////////////////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
switch (id)
{
case IDOK:
case IDCANCEL:
//用户单击退出按钮或按ESC键
EndDialog(hwnd, id);
break;
}
}
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
chSETDLGICONS(hwnd, IDD_SINGLETON);
g_hDlg = hwnd;
//检测是否有另一个实例正在运行
CheckInstance();
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
}
return FALSE;
}
/////////////////////////////////////////////////////////////////////////////////////////
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
//显示主窗口
DialogBox(hInstance, MAKEINTRESOURCE(IDD_SINGLETON), NULL, Dlg_Proc);
}
//resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 Singleton2.rc 使用
//
#define IDD_SINGLETON 101
#define IDI_SINGLETON 102
#define IDC_EDIT_DETAILS 1001
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 103
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1002
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
//Singleton2.rc
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_SINGLETON DIALOGEX 0, 0, 251, 161
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "单一实例"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "退出",IDOK,197,136,40,14
EDITTEXT IDC_EDIT_DETAILS,13,13,225,117,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY
END
/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
IDD_SINGLETON, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 244
TOPMARGIN, 7
BOTTOMMARGIN, 154
END
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_SINGLETON ICON "Singleton.ico"
#endif // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
3.3.3 复制对象句柄
(1)DuplicateHandle函数——获得一个进程句柄表中的一个记录项,然后在另一个进程的句柄表中创建这个记录项的一个副本。
参数 |
描述 |
HANDLE hSourceProcessHandle |
源进程内核句柄,即负责传递内核对象句柄的进程句柄 |
HANDLE hSourceHandle |
要传递的内核对象句柄 |
HANDLE hTargetProcessHandle |
目标进程内核句柄 |
PHANDLE phTargetHandle |
接收内核对象句柄的地址(先随便声明一个HANDLE) |
DWORD dwDesireAccess |
目标内核对象句柄使用何种访问掩码 |
BOOL hInheritHandle |
即目标内核对象的句柄是否可继承,会出现在目标进程句柄表相应的记录项中 |
DWORD dwOption |
DUPLICATE_SAME_ACCESS:表示与源内核对象所有标志一样,此时dwDesireAccess可标记为0。 DUPLICATE_CLOSE_SOURCE:传输完后,关闭源中的内核对象句柄,使用该标志,内核对象的计数就不会变。否则会加1,因为复制句柄,增加了一次引用。 |
★注意事项
①该函数能否成功调用与权限有关,即是否有足够的权限去操作目标进程
②如果函数执行成功,可以利用进程间通信将句柄值hTargetHandle传给目标进程,让他知道利用该值来使用内核对象。
③不要试图在调用函数进程中利用CloseHandle关闭hTargetHandle,因为这个句柄值属于目标进程,而不是调用函数的进程(注意句柄只是个索引,这种做法实际上关闭的可能仍然是调用进程中的某个内核对象,而不是目标进程的这个hTargetHandle对象)。
(2)举例说明DuplicateHandle的工作方式
设源进程S(Source)、目标进程T(Target)、调用函数的进程C(Caller)
①调用对象句柄复制函数前,各进程句柄表如下:
调用函数进程C的句柄表 只有两个表示进程的句柄值(注意进程也是内核对象) |
|||
索引 |
指向内核对象内存块的指针 |
访问掩码 (包含标志位的DWORD) |
标志 |
1 |
0xF0000000(S进程句柄值) |
0x???????? |
0x00000000 |
2 |
0xF0000010(T进程句柄值) |
0x???????? |
0x00000000 |
进程S的句柄表 (只包含一个内核对象,可任意类型的内核心对象,但不一定是进程内核对象) |
|||
1 |
0x00000000 |
(不可用) |
(不可用) |
2 |
0xF0000020(任意类型的内核对象,为要复制到目标进程的内核对象) |
0x???????? |
0x000000 |
进程T的句柄表 (复制前T只有一项,句柄值为2、句柄1没用) |
|||
1 |
0x00000000 |
(不可用) |
(不可用) |
2 |
0xF0000030(任意类型的内核对象) |
0x???????? |
0x000000 |
②调用DuplicateHandle(1,2,2,&hObj,0,TRUE,DUPLICATE_SAME_ACCESS) //句柄值!
索引 |
指向内核对象内存块的指针 |
访问掩码(包含标志位的DWORD) |
标志 |
1 |
0xF0000020 |
0x???????? |
0x000001 |
2 |
0xF0000030 |
0x???????? |
0x000000 |
注意:
A、进程S句柄表的第2项被复制到T的第1项(新的记录项被复制到第1项!)
B、因DUPLICATE_SAME_ACCESS,故进程T中的第1项掩码与S中的那项是一样的。
C、bInheritHandle设为TRUE,所以T句柄表中的标志为被设为1。
D、新的句柄值被保存在hObj变量中,要通过进程间通信通知目标进程。
【DuplicateHandle1程序】利用DuplicateHandle传递被更改访问权限的内核对象
#include <windows.h>
int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE,
LPTSTR szCmdLine, int nCmdShow) {
//w创建可读写的文件映射对象
HANDLE hFileMapRW = CreateFileMapping(INVALID_HANDLE_VALUE,
NULL, PAGE_READWRITE, 0, 10240, NULL);
// 创建另一个只读的文件映射对象
HANDLE hFileMapRO;
DuplicateHandle(GetCurrentProcess(), hFileMapRW, GetCurrentProcess(),
&hFileMapRO, FILE_MAP_READ, FALSE, 0);
// 使用该对象(因只读,所以对该对象进行任何写操作都会出错。
ReadFromTheFileMapping(hFileMapRO);
// 关闭只读的文件映射对象
CloseHandle(hFileMapRO);
// 这里我们可以继续使用可读写的对象.
...
// 关闭可读写对象
CloseHandle(hFileMapRW);
}
【DuplicateHandle2程序】——只是演示如果复制内核对象句柄,该程序实际不实现任何有意义的任务
#include <windows.h>
#include <tchar.h>
DWORD CALLBACK ThreadProc(PVOID pvParam);
int _tmain()
{
HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
HANDLE hMutexDup, hThread;
DWORD dwThreadID;
//复制互斥量对象,该内核对象计数加1
DuplicateHandle(GetCurrentProcess(), hMutex, GetCurrentProcess(), &hMutexDup,
0, FALSE, DUPLICATE_SAME_ACCESS);
//将该内核对象传递给子线程,有点类似于进程间通信,通知子线程可以用该对象了。
hThread = CreateThread(NULL, 0, ThreadProc, (LPVOID)hMutexDup, 0, &dwThreadID);
CloseHandle(hMutex);
WaitForSingleObject(hThread, INFINITE); //等待子线程执行完毕
CloseHandle(hThread); //关闭线程对象
return 0;
}
DWORD CALLBACK ThreadProc(PVOID pvParam)
{
//获取复制的内核对象
HANDLE hMutex = (HANDLE)pvParam;
//使用完后,在这里关闭内核对象!
CloseHandle(hMutex);
return 0;
}