在 Vista 下,在没有 UAC 弹出窗口的情况下启动您的应用程序,并以本地系统帐户运行
本文介绍了如何使用服务辅助应用程序,从会话 0 启动应用程序到会话 1,并以本地系统帐户运行。
引言
在 Vista 中,我遇到了一个问题,我想在管理员账户下启动一个 exe 来执行某些任务。我无法做到,因为会弹出 UAC 提升对话框,用户必须对此进行响应。为了解决这个问题,我提出了一种解决方案,即从服务将应用程序启动到当前用户会话中,并具有本地系统账户的权限。这样就不需要响应 UAC 对话框。通过这种方式,我可以以最高权限运行我的应用程序,避免 UAC 对话框。在我的情况下,这是必要的,因为我需要在没有用户干预的情况下控制/与以管理员权限运行的应用程序进行通信,并在系统启动时在当前用户会话中执行一些管理任务。
背景
我使用的概念非常简单。我有一个普通的 C++ 用户模式应用程序,它通过自定义消息与服务进行通信。收到自定义消息后,服务将以系统账户启动我想要的目标应用程序。为了获得本地系统账户,我使用了所需会话中的 winlogon 的令牌,因为 *winlogon.exe* 以本地系统账户运行。(在 Vista 中,服务存在于会话 0 中,第一个用户登录到会话 1,以此类推,与 XP 不同)
使用代码
首先,让我们回顾一下文件 *CustomMessageSender.cpp*。这是与服务通信的用户模式应用程序。该应用程序可以是任何没有特殊权限的普通应用程序。
#define SERVICE_NAME _T("CustomSvc")
//CUSTOM MESSAGE FOR SERVICE TO LAUNCH THE PROCESS INTO SESSION 1
#define SERVICE_CONTROL_CUSTOM_MESSAGE 0x0085
int _tmain(int argc, _TCHAR* argv[])
{
SC_HANDLE hMyService,hSCM;
BOOL bSuccess;
SERVICE_STATUS status;
hSCM = OpenSCManager(0,0,SC_MANAGER_CONNECT);
if(!hSCM)
{
printf("Open SCM failed with error %u",GetLastError());
}
hMyService = OpenService(hSCM,SERVICE_NAME,SERVICE_USER_DEFINED_CONTROL);
if(!hMyService)
{
printf("Open SCM failed with error %u",GetLastError());
}
bSuccess = ControlService(hMyService,SERVICE_CONTROL_CUSTOM_MESSAGE,&status);
if(!bSuccess)
{
printf("Control Service failed with error %u",GetLastError());
}
CloseServiceHandle(hMyService);
CloseServiceHandle(hSCM);
return 0;
}
上面的代码非常简单明了。我使用了 `SERVICE_USER_DEFINED_CONTROL` 和 `SC_MANAGER_CONNECT` 访问权限,因为任何用户模式应用程序都可以连接到我们的服务以发送自定义消息。不需要管理员权限。因此,此应用程序将 `SERVICE_CONTROL_CUSTOM_MESSAGE` 发送到服务。接收消息并启动应用程序的服务部分代码如下。我使用了 Anish 在 Code Project 上发布的一个示例服务,并在其中添加了我的功能。
//CUSTOM MESSAGE FOR SERVICE TO LAUNCH AN APP INTO SESSION 1
#define SERVICE_CONTROL_CUSTOM_MESSAGE 0x0085
//Method to launch an application into session 1 under
//the local system account
BOOL LaunchAppIntoDifferentSession();
.
.
.
.
.
.
.
.
.
void WINAPI ServiceCtrlHandler(DWORD Opcode)
{
switch(Opcode)
{
//////////////////////////////////////////////////////////////////////////
//Added By Jaisvar on 04/11/07 to receive a custom message from a user app
case SERVICE_CONTROL_CUSTOM_MESSAGE:
LaunchAppIntoDifferentSession();
break;
在上面的代码片段中,我声明了自定义消息以及接收自定义消息时将执行的方法的原型。在我展示代码片段之前,让我先描述一下 `LaunchAppIntoDifferentSession` 方法。要以本地系统账户启动进程,我执行以下步骤:
- 使用 `WTSGetActiveConsoleSessionId` 获取活动的控制台会话 ID。
- 由于我需要以系统账户启动应用程序,我使用了 Winlogon 的令牌,因为 Winlogon 以系统账户运行。因此,我获取 Winlogon 的进程 ID 并复制令牌。
- 然后,我确保将 `STARTUPINFO` 参数 `lpDesktop` 设置为 *winsta0\Default*,因为我需要在此处启动我的进程。
- 然后,我使用 Winlogon 的复制令牌调用 `CreateProcessAsUser`,将我的进程启动到会话 1。
- 就这样。我完成了。
BOOL LaunchAppIntoDifferentSession()
{
PROCESS_INFORMATION pi;
STARTUPINFO si;
BOOL bResult = FALSE;
DWORD dwSessionId,winlogonPid;
HANDLE hUserToken,hUserTokenDup,hPToken,hProcess;
DWORD dwCreationFlags;
// Log the client on to the local computer.
dwSessionId = WTSGetActiveConsoleSessionId();
//////////////////////////////////////////
// Find the winlogon process
////////////////////////////////////////
PROCESSENTRY32 procEntry;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE)
{
return 1 ;
}
procEntry.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hSnap, &procEntry))
{
return 1 ;
}
do
{
if (_stricmp(procEntry.szExeFile, "winlogon.exe") == 0)
{
// We found a winlogon process...
// make sure it's running in the console session
DWORD winlogonSessId = 0;
if (ProcessIdToSessionId(procEntry.th32ProcessID, &winlogonSessId)
&& winlogonSessId == dwSessionId)
{
winlogonPid = procEntry.th32ProcessID;
break;
}
}
} while (Process32Next(hSnap, &procEntry));
////////////////////////////////////////////////////////////////////////
WTSQueryUserToken(dwSessionId,&hUserToken);
dwCreationFlags = NORMAL_PRIORITY_CLASS|CREATE_NEW_CONSOLE;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb= sizeof(STARTUPINFO);
si.lpDesktop = "winsta0\\default";
ZeroMemory(&pi, sizeof(pi));
TOKEN_PRIVILEGES tp;
LUID luid;
hProcess = OpenProcess(MAXIMUM_ALLOWED,FALSE,winlogonPid);
if(!::OpenProcessToken(hProcess,TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY
|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY|TOKEN_ADJUST_SESSIONID
|TOKEN_READ|TOKEN_WRITE,&hPToken))
{
int abcd = GetLastError();
printf("Process token open Error: %u\n",GetLastError());
}
if (!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid))
{
printf("Lookup Privilege value Error: %u\n",GetLastError());
}
tp.PrivilegeCount =1;
tp.Privileges[0].Luid =luid;
tp.Privileges[0].Attributes =SE_PRIVILEGE_ENABLED;
DuplicateTokenEx(hPToken,MAXIMUM_ALLOWED,NULL,
SecurityIdentification,TokenPrimary,&hUserTokenDup);
int dup = GetLastError();
//Adjust Token privilege
SetTokenInformation(hUserTokenDup,
TokenSessionId,(void*)dwSessionId,sizeof(DWORD));
if (!AdjustTokenPrivileges(hUserTokenDup,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,NULL))
{
int abc =GetLastError();
printf("Adjust Privilege value Error: %u\n",GetLastError());
}
if (GetLastError()== ERROR_NOT_ALL_ASSIGNED)
{
printf("Token does not have the provilege\n");
}
LPVOID pEnv =NULL;
if(CreateEnvironmentBlock(&pEnv,hUserTokenDup,TRUE))
{
dwCreationFlags|=CREATE_UNICODE_ENVIRONMENT;
}
else
pEnv=NULL;
// Launch the process in the client's logon session.
bResult = CreateProcessAsUser(
hUserTokenDup, // client's access token
_T("C:\\SessionLauncher\\a.exe"), // file to execute
NULL, // command line
NULL, // pointer to process SECURITY_ATTRIBUTES
NULL, // pointer to thread SECURITY_ATTRIBUTES
FALSE, // handles are not inheritable
dwCreationFlags, // creation flags
pEnv, // pointer to new environment block
NULL, // name of current directory
&si, // pointer to STARTUPINFO structure
&pi // receives information about new process
);
// End impersonation of client.
//GetLastError Shud be 0
int iResultOfCreateProcessAsUser = GetLastError();
//Perform All the Close Handles tasks
CloseHandle(hProcess);
CloseHandle(hUserToken);
CloseHandle(hUserTokenDup);
CloseHandle(hPToken);
return 0;
}
这样,普通的用户模式应用程序就可以向服务发送自定义消息,以在本地系统账户下启动自身,而无需弹出 UAC 对话框。
一些读者想知道如何
- 让 `GetUserName()` API 返回当前登录用户。
- 以系统账户访问 HKCU。
好吧,从用户模式来看,当我们启动需要提升的进程时,几乎不可能绕过 UAC 对话框,因为它是微软这样设计的。尽管通过编写一些内核模式代码(DKOM 概念)可能会实现这些更改。但从用户模式来看,我能想到的最好的办法就是冒充系统账户作为登录用户来访问 HKCU。我为此使用了 Explorer 进程,因为它以用户账户运行。
冒充用户令牌将导致当前工作线程在用户上下文中运行。请注意,如果您使用 `CreateProcess()`,它仍然会在系统账户下生成进程,因为我们整个进程仍然在本地系统账户下运行。
为此编写的代码列在下面的代码片段中。这段代码需要写入由服务启动的应用程序中。我没有将这部分代码包含在“*SessionLaucher.zip*”中。
DWORD dwSessionId,dwExplorerLogonPid,dwSize,dwRegDataSize;
HANDLE hProcess,hPToken;
char szUserName[MAX_PATH];
char szRegData[MAX_PATH];
char szRegPath[500] = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";
HKEY hKey; //Handle to registry Key
long lRegResult; //Registry operation result
//Get the active desktop session id
dwSessionId = WTSGetActiveConsoleSessionId();
//We find the explorer process since it will have the user token
//////////////////////////////////////////
// Find the explorer process
////////////////////////////////////////
PROCESSENTRY32 procEntry;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE)
{
return 1 ;
}
procEntry.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hSnap, &procEntry))
{
return 1 ;
}
do
{
if (_stricmp(procEntry.szExeFile, "explorer.exe") == 0)
{
DWORD dwExplorerSessId = 0;
if (ProcessIdToSessionId(procEntry.th32ProcessID, &dwExplorerSessId)
&& dwExplorerSessId == dwSessionId)
{
dwExplorerLogonPid = procEntry.th32ProcessID;
break;
}
}
} while (Process32Next(hSnap, &procEntry));
////////////////////////////////////////////////////////////////////////
hProcess = OpenProcess(MAXIMUM_ALLOWED,FALSE,dwExplorerLogonPid);
if(!::OpenProcessToken(hProcess,TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY
|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY|TOKEN_ADJUST_SESSIONID
|TOKEN_READ|TOKEN_WRITE,&hPToken))
{
int abcd = GetLastError();
printf("Process token open Error: %u\n",GetLastError());
}
我们需要冒充服务令牌以用户身份运行,以访问注册表。这将导致我们的工作线程以用户令牌的上下文运行。
//Impersonate the explorer token which runs under the user account
ImpersonateLoggedOnUser(hPToken);
int iImpersonateResult = GetLastError();
if(iImpersonateResult == ERROR_SUCCESS)
{
//GetUserName will now return the username
GetUserName(szUserName,&dwSize);
//Since the thread is running as the user we can access the HKCU now
dwRegDataSize = sizeof(szRegData);
lRegResult = RegOpenKeyEx(HKEY_CURRENT_USER,
szRegPath,0,KEY_QUERY_VALUE,&hKey);
if (lRegResult == ERROR_SUCCESS)
RegQueryValueEx(hKey,_T("SideBar"),NULL,NULL,
(LPBYTE)&szRegData,&dwRegDataSize);
}
//Once the operation is over revert back to system account.
RevertToSelf();
我使用 V.Anish 发布的简单服务来添加获取 Winlogon 令牌并启动应用程序的代码。可以通过键入 `service.exe -i` 来安装它。然后需要启动服务才能使其运行。
如果您有任何疑问,可以发送邮件至 jaisvar@gmail.com 与我联系。
在我的下一篇文章中,我将讨论更多关于调整 UAC 的概念。