本章在LocalSystem账户下通过一个服务辅助程序来介绍如何从session 0到session 1启动一个应用程序。
在Vista系统中,遇到的问题是在administrator账户下想要启动一个exe程序来执行一些任务,但是由于弹出的UAC对话框让用户不得不做出去做一些操作而导致失败。围绕这个问题,想到了通过服务来启动应用程序,这过程会以LocalSystem账号权限切换到当前用户的session。这就解决了弹出要求用户给出响应的UAC对话框问题了。在这种方式下,可以让应用程序以最高权限运行,绕过UAC对话框。在这种情况下,用这种方式是必须的,因为要在没有用户干预的情况下,与以administrator权限的应用程序进行通信以及做一些控制操作,并且在系统启动时在当前用户session内做一些administrative的任务操作。
这儿有一个普通用户模式的应用程序要通过客户端消息与服务进行通信。一旦服务接收到客户端消息,服务就要在系统账户下启动所指定的应用程序。为了获取到一个LocalSystem账户的token,这儿使用了winlogon的token,由于winlogon.exe就是运行在LocalSystem账户下的。(在Vista中,服务是处于session 0中的并且第一个用户以session 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访问权限是因为任何用户模式应用程序都能够与服务连接以发送客制化的消息。不要求有admin权限。所以该应用程序发送SERVICE_CONTROL_CUSTOM_MESSAGE到服务。代码的服务部分接收消息并启动下面的应用程序。
//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方法。为了在LocalSystem下启动一个进程,做了以下步骤:
1.使用函数WTSGetActiveConsoleSessionId 获取激活控制台的session ID。
2.由于必须在系统账户下启动应用程序,因此就使用来自Winlogon的token,由于Winlogon是运行在系统账户下的。因此获取到Winlogon的进程ID并复制token。
3.随后确保发送startupinfo参数lpDesktop到winsta0\default,由于需要在那儿启动线程。
4.随后使用带Winlogon的复制token参数的CreateProcessAsUser来启动进程到session 1.
5.完成
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;
}
使用这种方式之后,一个普通用户模式应用程序就能够通过发送客户消息到服务,在LocalSystem账户下来启动它自己了,并且不会有UAC对话框弹窗的出现。
为什么这就能阻止弹窗出现呢?
1.使用GetUserName() API获取当前登录用户名
2.在系统账户下访问HKCU
当启动一个要求提权的进程时,从用户空间去绕过UAC对话框几乎是不可能的,由于这是Microsoft设计好的。尽管通过改变内核模式代码(DKOM概念)可能办到,但是仍然是从用户空间的,这儿能够想到的最好方式就是模拟系统账户扮演当前登录用户来访问HKCU。这儿使用Explorer进程来达到目的,由于它是运行在用户账户下的。
模拟用户token会引起当前工作现场运行在用户上下文中。记住若使用CreateProcess()它仍然会在系统账户下创建线程,由于整个过程仍然运行在LocalSystem账户下。
下面是代码,这些代码要写入到会被服务启动的应用程序中去。
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());
}
需要模拟服务的token来以用户身份运行以访问注册表。这会让工作线程运行在用户token的上下文中。
//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();
参考网址