操作系统服务和用户进程进行通信
以上的例子,展示了如何在服务中显示用户界面到用户桌面。这只是系统服务因为Session 0隔离而遇到的第一类问题。如果系统服务想与用户进程进行通信,又该如何处理呢?在这种情况下,我们可以使用Windows Communication Foundation (WCF), .NET remoting, 命名管道(named pipes)或者是其他的进程通信(interprocess communication ,IPC))机制(除了Windows消息之外)在Session之间进行通信。有人可能要问,Session 0隔离本身是为了系统安全而采取的保护措施,如果在隔离的同时又允许系统服务和用户进程进行通信,那岂不是Session 0隔离毫无意义?实际上,隔离并不是完全意义上的隔断。Session 0隔离后,我们需要以更加安全的方式进行操作系统服务和用户进程之间的交互和通信。
安全通讯和其他共享对象(例如,命名管道,文件映射),通过使用自由访问控制列表(DACL)来加强用户组访问权限的控制。同时我们可以使用一个系统访问控制列表(SACL),以确保中低权限的进程可以访问共享对象,即使这个对象是一个系统或更高权限的服务所创建的。下面这段代码,就演示了如何通过DACL权限,访问系统服务所创建的全局名字空间的核心对象(事件)。
{
// 获取当前的Session ID和用户令牌
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken = NULL;
if (WTSQueryUserToken(dwSessionID, &hToken) == FALSE)
{
goto Cleanup;
}
// 获取用户的SID(security identifier)
// 注意这里我们两次调用了GetTokenInformation函数
// 第一次是为了获取TKOEN_USER结构体的大小
// 第二次才是真正地获取信息,填充这个结构体
DWORD dwLength;
TOKEN_USER* account = NULL;
if (GetTokenInformation(hToken, TokenUser, NULL, 0, &dwLength) == FALSE &&
GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
goto Cleanup;
}
account = (TOKEN_USER*)new BYTE[dwLength];
if (GetTokenInformation(hToken, TokenUser,
(LPVOID)account, dwLength, &dwLength) == FALSE)
{
goto Cleanup;
}
// 在这里,我们调用ConvertSidToStringSid函数将
// 用户的SID转换成SID字符串,然后通过SID字符串我们创建一个SDDL字符串,
// 有了SDDL字符串之后,我们可以创建一个安全描述器(Security Descriptor)。
// 而这个安全描述器,是我们在后面创建全局对象所需要的.
LPWSTR lpszSid = NULL;
if (ConvertSidToStringSid(account->User.Sid, &lpszSid) == FALSE)
{
goto Cleanup;
}
WCHAR sddl[1000];
wsprintf(sddl, L"O:SYG:BAD:(A;;GA;;;SY)(A;;GA;;;%s)S:(ML;;NW;;;ME)", lpszSid);
// 转换SDDL字符串到一个安全描述器对象
PSECURITY_DESCRIPTOR sd = NULL;
if (ConvertStringSecurityDescriptorToSecurityDescriptor(sddl,
SDDL_REVISION_1, &sd, NULL) == FALSE)
{
goto Cleanup;
}
// 用上面创建的安全描述器对象初始化SECURITY_ATTRIBUTES结构体
SECURITY_ATTRIBUTES sa;
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = sd;
sa.nLength = sizeof(sa);
// 创建全局名字空间的事件
// 这里需要注意的是,全局名字空间的对象都需要有Global的前缀
g_hAlertEvent = CreateEvent(&sa, FALSE, FALSE, L"Global\\AlertServiceEvent");
if (g_hAlertEvent == NULL)
{
goto Cleanup;
}
while (!g_Stop)
{
Sleep(5000);
// 发送一个事件
SetEvent(g_hAlertEvent);
}
// 清理工作
Cleanup:
if (hToken != NULL)
CloseHandle(hToken);
if (account != NULL)
delete[] account;
if (lpszSid != NULL)
LocalFree(lpszSid);
if (sd != NULL)
LocalFree(sd);
if (g_hAlertEvent == NULL)
CloseHandle(g_hAlertEvent);
return 0;
}
在这段代码中,我们通过用户令牌,获取用户的SID,然后通过SID和SDDl的转换,创建了安全描述器对象,并通过这个安全描述器对象最终创建了具有合适访问控制的全局名字空间的对象。现在,在客户端我们就可以顺利的访问这个全局名字空间的对象,与之进行通信了。
#include <stdio.h>
int main()
{
// 打开全局名字空间的共享事件对象
// 注意,这里我们同样适用了Global前缀
HANDLE hEvent = OpenEvent(SYNCHRONIZE, FALSE, L"Global\\AlertServiceEvent");
if (hEvent == NULL)
{
printf("无法打开服务事件: %d\n", GetLastError());
return -1;
}
while (TRUE)
{
printf("等待服务事件...\n");
WaitForSingleObject(hEvent, INFINITE);
printf("获得服务事件!\n");
}
return 0;
}
牛郎织女隔着银河还有鹊桥来沟通,所以系统服务和用户桌面之间的Session 0隔离,也有相应的方式来完成它们之间的交互和通信。只是Session 0的隔离,对各种交互和通信方式的安全性提出了更高的要求。
系列文章索引: