显示更复杂的UI
如果我们不满足于仅仅显示一个消息对话框,而需要从系统服务显示一个更加复杂的用户界面,这时我们可以使用CreateProcessAsUser函数在用户的桌面上创建一个新的进程来显示更加复杂的用户界面,而这个进程虽然是由系统服务创建,但是却是运行在用户环境下。以下的代码演示了创建进程显示复杂UI的过程:
DWORD WINAPI TimeServiceThread(LPVOID)
{
while (!g_Stop)
{
Sleep(5000);
// 为了显示更加复杂的用户界面,我们需要从Session 0创建
// 一个进程,但是这个进程是运行在用户环境下。
// 我们可以使用CreateProcessAsUser实现这一功能。
BOOL bSuccess = FALSE;
STARTUPINFO si = {0};
// 进程信息
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
// 获得当前Session ID
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken = NULL;
// 获得当前Session的用户令牌
if (WTSQueryUserToken(dwSessionID, &hToken) == FALSE)
{
goto Cleanup;
}
// 复制令牌
HANDLE hDuplicatedToken = NULL;
if (DuplicateTokenEx(hToken,
MAXIMUM_ALLOWED, NULL,
SecurityIdentification, TokenPrimary,
&hDuplicatedToken) == FALSE)
{
goto Cleanup;
}
// 创建用户Session环境
LPVOID lpEnvironment = NULL;
if (CreateEnvironmentBlock(&lpEnvironment,
hDuplicatedToken, FALSE) == FALSE)
{
goto Cleanup;
}
// 获得复杂界面的名字,也就是获得可执行文件的路径
WCHAR lpszClientPath[MAX_PATH];
if (GetModuleFileName(NULL, lpszClientPath, MAX_PATH) == 0)
{
goto Cleanup;
}
PathRemoveFileSpec(lpszClientPath);
wcscat_s(lpszClientPath,
sizeof(lpszClientPath)/sizeof(WCHAR),
L"\\TimeServiceClient.exe");
// 在复制的用户Session下执行应用程序,创建进程。
// 通过这个进程,就可以显示各种复杂的用户界面了
if (CreateProcessAsUser(hDuplicatedToken,
lpszClientPath, NULL, NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
lpEnvironment, NULL, &si, &pi) == FALSE)
{
goto Cleanup;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
bSuccess = TRUE;
// 清理工作
Cleanup:
if (!bSuccess)
{
ShowMessage(L"无法创建复杂UI", L"错误");
}
if (hToken != NULL)
CloseHandle(hToken);
if (hDuplicatedToken != NULL)
CloseHandle(hDuplicatedToken);
if (lpEnvironment != NULL)
DestroyEnvironmentBlock(lpEnvironment);
}
return 0;
}
{
while (!g_Stop)
{
Sleep(5000);
// 为了显示更加复杂的用户界面,我们需要从Session 0创建
// 一个进程,但是这个进程是运行在用户环境下。
// 我们可以使用CreateProcessAsUser实现这一功能。
BOOL bSuccess = FALSE;
STARTUPINFO si = {0};
// 进程信息
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
// 获得当前Session ID
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken = NULL;
// 获得当前Session的用户令牌
if (WTSQueryUserToken(dwSessionID, &hToken) == FALSE)
{
goto Cleanup;
}
// 复制令牌
HANDLE hDuplicatedToken = NULL;
if (DuplicateTokenEx(hToken,
MAXIMUM_ALLOWED, NULL,
SecurityIdentification, TokenPrimary,
&hDuplicatedToken) == FALSE)
{
goto Cleanup;
}
// 创建用户Session环境
LPVOID lpEnvironment = NULL;
if (CreateEnvironmentBlock(&lpEnvironment,
hDuplicatedToken, FALSE) == FALSE)
{
goto Cleanup;
}
// 获得复杂界面的名字,也就是获得可执行文件的路径
WCHAR lpszClientPath[MAX_PATH];
if (GetModuleFileName(NULL, lpszClientPath, MAX_PATH) == 0)
{
goto Cleanup;
}
PathRemoveFileSpec(lpszClientPath);
wcscat_s(lpszClientPath,
sizeof(lpszClientPath)/sizeof(WCHAR),
L"\\TimeServiceClient.exe");
// 在复制的用户Session下执行应用程序,创建进程。
// 通过这个进程,就可以显示各种复杂的用户界面了
if (CreateProcessAsUser(hDuplicatedToken,
lpszClientPath, NULL, NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
lpEnvironment, NULL, &si, &pi) == FALSE)
{
goto Cleanup;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
bSuccess = TRUE;
// 清理工作
Cleanup:
if (!bSuccess)
{
ShowMessage(L"无法创建复杂UI", L"错误");
}
if (hToken != NULL)
CloseHandle(hToken);
if (hDuplicatedToken != NULL)
CloseHandle(hDuplicatedToken);
if (lpEnvironment != NULL)
DestroyEnvironmentBlock(lpEnvironment);
}
return 0;
}
在这段代码中,我们首先获得了当前的Session ID,然后通过Session ID,我们获得用户令牌。有了用户令牌后,我们就可以创建一个相同的用户环境了,而最终我们所创建的复杂界面进程将在这个环境下运行和显示。完成这些准备工作后,我们利用CreateProcessAsUser函数在复制的用户环境下创建新的进程,显示复杂的用户界面。用这种方式创建的进程,不会受到“Interactive Service Detection”对话框的打扰而直接显示到用户桌面上,这跟从当前用户Session执行应用程序并无太大的差别。
图5 从系统服务显示的复杂界面