一个远程桌面的服务可以为自己的命名内核对象设置多个命名空间,这些内核对象包括:event,semaphores,mutexes,waitable timers,file-mapping,job object。在client/server类型的应用程序中,服务都有一个全局命名空间global namespace。除此之外,每个客户会话都可以有自己单独的命名空间,来放置自己独有的内核对象。
独立的客户会话命名空间能够让多个客户运行同一个应用程序而不互相干扰。在一个会话中使用使用会话名称作为内核对象的缺省命名空间,另外,进程还可以使用全局命名空间,使用的方法是在内核对象的名字前,加上“Global\”前缀。例如,下面的代码调用CreateEven在全局命名空间中创建一个CSAPP命名对象。
CreateEvent(NULL,FALSE,FALSE,”Global\\CSAPP”);
远程桌面环境中的服务应用程序将全局命名空间Global namespace作为缺省命名空间。会话0(session 0)只被主机服务使用,他没有控制台会话。
全局命名空间
- 全局命名空间能够让多个客户会话和服务应用程序进行交互。
例如,一个client/server应用程序可能会使用一个互斥对象mutex object来同步自己的线程。服务组件能够在全局命名空间中创建互斥对象。一个客户会话能够使用“Global\”前缀,来打开互斥对象。
全局命名空间global namespace还有一个用处,就是应用程序使用命名对象,
- 检查是否已经有一个应用程序的实例已经在系统中的某个会话下运行
来检查是否已经有一个应用程序的实例已经在系统中的某个会话下运行。这个命名对象必须在全局命名空间中创建,而不能在某个会话的命名空间中创建。更普遍的是检查某个会话下,是否有多个程序的实例在运行。
本地命名空间
如果要在本会话下创建一个命名对象,也就是在会话的命名空间中,创建内核对象,那么必须在内核对象前加上”Local\”前缀!注意这个关键字是大小写敏感的。
CreateMutex(NULL,FALSE,L”Local\\ydm”);
“Session\”前缀被系统保留,不要使用这个前缀来创建命名内核对象。
windows快速用户切换,是通过远程桌面服务会话来实现的。第一个用户登陆后,其会话为会话1(Session 1),第二个用户的会话为会话2,以此类推。内核对象的名字必须遵守远程桌面服务的准则,否则应用程序不能支持多用户。
创建全局内核对象命名空间
创建全局的file-mapping命名对象,有一些其他的要求。如果在非session 0的会话中,创建file-mapping命名对象的话,必须要有一定的特权。因此,一个运行在任意远程桌面上的应用程序必须有SeCreateGlobalPrivilege权限,才能创建全局的file-mapping对象,所幸的是,只有file-mapping命名对象有这个要求。file-mapping命名对象的打开,没有这个要求。
HANDLE WINAPI CreateBoundaryDescriptor(
__in LPCTSTR Name,
__in ULONG Flags
);
创建一个边界描述符:
Name:边界描述符的名字,这个名字由你自己来取;
Flags:保留,=NULL。
返回值:
如果函数成功,那么返回值就是一个边界描述符的句柄。
如果函数失败,返回值NULL。
备注:
一个新的边界描述符至少要有一个安全描述符SID。可以使用AddSIDToBoundaryDescriptor函数,为边界描述符增加SID。
BOOL WINAPI AddSIDToBoundaryDescriptor(
__inout HANDLE *BoundaryDescriptor,
__in PSID RequiredSid
);
将一个安全描述符SID添加到指定的边界描述符中
BoundaryDescriptor:一个指向边界描述符的句柄。CreateBoundaryDescriptor函数返回这个句柄。
RequiredSid:一个指向安全描述符Sid的指针。
返回值:
如果函数成功,返回值是非零。
如果函数失败,返回零。
备注:
AddSIDToBoundaryDescriptor函数必须为每个SID调用一次,将其增加到边界描述符中。
BOOL WINAPI CreateWellKnownSid(
__in WELL_KNOWN_SID_TYPE WellKnownSidType,
__in_opt PSID DomainSid,
__out_opt PSID pSid,
__inout DWORD *cbSid
);
创建一个有预定义别名的SID
WellKnownSidType:WELL_KNOWN_SID_TYPE列举的成员,用这个成员标识要获取的SID。
DomainSid:一个指向SID的指针,这个SID代表了创建SID所使用的域控制安全描述符,如果是本地计算机,那么使用NULL。
pSid:一个指向内存块的指针,CreateWellKnownSid函数会将创建好的新SID保存到这个内存块中。
cbSid:一个DWORD类型的指针。代表了生成的pSid的大小。
返回值:
如果函数成功,返回非零,否则返回零。
HANDLE WINAPI CreatePrivateNamespace(
__in_opt LPSECURITY_ATTRIBUTES lpPrivateNamespaceAttributes,
__in LPVOID lpBoundaryDescriptor,
__in LPCTSTR lpAliasPrefix
);
创建一个私有命名空间
lpPrivateNamespaceAttributes:一个指向SECURITY_ATTRIBUTES结构的指针,这个结构定义了命名空间的安全属性。
lpBoundaryDescriptor:一个描述符,定义了命名空间如何被隔离。调用者必须在这个边界内。函数CreateBoundaryDescriptor函数创建一个边界描述符。
lpAliasPrefix:命名空间的前缀。为了在这个命名空间中创建命名对象,定义对象名称的前缀,形式为”prefix\”对象名称。系统支持不同边界中,可以有相同的私有命名空间。
返回值:
如果函数成功,返回一个指向新命名空间的句柄,否则返回NULL。
备注:
其他的应用程序,可以使用OpenPrivateNamespac函数,来打开一个已经存在的命名空间。创建命名控件的程序,使用ClosePrivateNamespace函数来关闭指向命名控件的句柄。当创建这个命名空间的进程退出时,被创建的命名空间也会被关闭,那么此后在使用OpenPrivateNamespace函数,就会失败,但这并不影响对该命名控件中的对象的操作。
HANDLE WINAPI OpenPrivateNamespace(
__in LPVOID lpBoundaryDescriptor,
__in LPCTSTR lpAliasPrefix
);
打开一个私有命名空间。
lpBoundaryDescriptor:定义私有命名空间如何被隔离的描述符。调用这个函数的程序必须在这个边界之内。函数CreateBoundaryDescriptor创建一个边界描述符。
lpAliasPrefix:命名空间前缀。为了在这个私有命名空间中创建一个命名对象,那么这个对象名称的前缀必须是lpAliasPrefix\。系统支持多个私有命名空间具有相同的别名,只要边界描述符不同就可以了。
返回值:
函数返回一个已经存在的私有命名空间的句柄。
代码实现:
#include<Windows.h>
#include<tchar.h>
#include"resource.h"
#include<Sddl.h>
#include<Strsafe.h>
HANDLE h_Bundary;//边界描述符句柄
HANDLE h_Namespace;//私有命名空间句柄
HANDLE hMutex;//内核对象句柄
BOOL opened=FALSE; //使用ClosePrivateNamespace函数,销毁私有命名空间的时候,需要知道
//私有命名空间是否是本程序创建的,如果是,那么这个ClosePrivateNamespace函数的第二个参数
//为1,否则为0.
INT_PTR CALLBACK Dlg_pro(
__in HWND hwndDlg,
__in UINT uMsg,
__in WPARAM wParam,
__in LPARAM lParam
);
BOOL CheckInstance(HWND);
int APIENTRY _tWinMain(HINSTANCE hInstance,HINSTANCE,LPTSTR lpCmdLine,int nCmdShow)
{
DialogBox(hInstance,MAKEINTRESOURCE(IDD_DIALOG1),NULL,Dlg_pro);
if(hMutex)
CloseHandle(hMutex);//杀死内核对象
if(h_Namespace)//销毁私有命名空间。
{
if(opened)
ClosePrivateNamespace(h_Namespace,0);
//如果是打开私有命名空间,ClosePrivateNamespace的第二个参数是0
else
ClosePrivateNamespace(h_Namespace,1);
//如果是创建的私有命名空间,ClosePrivateNamespace的第二个参数是1
}
if(h_Bundary)
DeleteBoundaryDescriptor(h_Bundary);//销毁边界描述符!!
return 0;
}
INT_PTR CALLBACK Dlg_pro(
__in HWND hwndDlg,
__in UINT uMsg,
__in WPARAM wParam,
__in LPARAM lParam
)
{
if(uMsg==WM_INITDIALOG)//初始化对话框消息
{
SetDlgItemText(hwndDlg,IDC_STATIC1,L"");//将静态文本框中的内容清空。
CheckInstance(hwndDlg);//真正的检查函数
}
if(uMsg==WM_COMMAND)//退出消息
{
if(HIWORD(wParam)==BN_CLICKED&&LOWORD(wParam)==IDCANCEL)
EndDialog(hwndDlg,0);
}
return 0;
}
BOOL CheckInstance(HWND hwnd)
{
h_Bundary=CreateBoundaryDescriptor(L"ydm",0);//创建一个边界描述符!
BYTE LocalAdmiSID[SECURITY_MAX_SID_SIZE];
DWORD cbSID=sizeof(LocalAdmiSID);
CreateWellKnownSid(WinBuiltinAdministratorsSid,NULL,&LocalAdmiSID,&cbSID);
//创建一个管理员SID
AddSIDToBoundaryDescriptor(&h_Bundary,&LocalAdmiSID);
//将管理员SID加入到边界描述符中,此后,只有管理员才能够打开这个边界描述符对应的私有命名空间!
SECURITY_ATTRIBUTES sa;
sa.nLength=sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle=FALSE;
ConvertStringSecurityDescriptorToSecurityDescriptor(L"D:(A;;GA;;;BA)",SDDL_REVISION_1,
&sa.lpSecurityDescriptor,NULL);//创建了一个安全描述符:
/*
D: 这个代表这个安全描述符中有DACL-discretionary Access Control list
A 表示这个安全描述符是一个允许安全描述符,如果是D-deny,那么表示拒绝安全描述符allow
GA: 表示访问的权限是所有权限;
BA: 代表了管理员SID
这个"D:(A;;GA;;;BA)"字符串是按照:Security Descriptor Definition Language语言,来编写的。
*/
h_Namespace=CreatePrivateNamespace(&sa,h_Bundary,L"ygg");
//创建私有命名空间!!!注意,最后一个参数只是这个命名空间的别名!!!
if(GetLastError()==ERROR_ALREADY_EXISTS)//如果私有命名空间已经创建,那么我们打开这个私有命名空间!
{
h_Namespace=OpenPrivateNamespace(h_Bundary,L"ygg");
//打开私有命名空间
opened=TRUE;//设置打开标志,用于ClosePrivateNamespace函数最后一个参数的设置!
}
LocalFree(sa.lpSecurityDescriptor);
//ConvertStringSecurityDescriptorToSecurityDescriptor函数,会自动给被创建的安全描述符分配内存
//当这个安全描述符不再使用时,使用LocalFree函数释放他。
TCHAR szMutex[64];
StringCchPrintf(szMutex,_countof(szMutex),L"%s\\%s",L"ygg",L"mutex");
//形成一个L"ygg\\mutex"字符串!
hMutex=CreateMutex(NULL,FALSE,szMutex);//在ygg命名空间中,创建一个互斥内核对象!
if(GetLastError()==ERROR_ALREADY_EXISTS)
{
SetDlgItemText(hwnd,IDC_STATIC1,L"已经存在一个程序");
}
else
{
SetDlgItemText(hwnd,IDC_STATIC1,L"这是第一个程序实例");
}
return TRUE;
}
总结:
- 创建一个边界标识符BoundaryDescriptor;
CreateBoundaryDescriptor; - 获取一个SID;
CreateWellKnownSid - 将这个SID放到边界标识符中,只有具有这个SID的程序,才能打开这个边界标识符;
AddSidToBoundaryDescriptor
//
你可以将这个函数理解为,为边界描述符创建边界!!!
// - 创建一个安全描述符(允许访问安全描述符);
ConvertStringSecurityDescriptorToSecurityDescriptor - 用这个边界描述符和安全描述符,来创建一个私有命名空间,同时给这个私有命名空间一个别名,例如:”ydm”;
CreatePrivateNamespace(Boundary_Handle,&Name_Handle,L”ydm”);
OpenPrivateNamespace; - 要在这个私有命名空间中,创建一个内核对象,例如一个Mutex,那么如下就可以了:
CreatetMutex(NULL,FALSE,L”ydm\object_name”);