Introduction
In Vista, I came across a problem where I wanted to launch an exe under an administrator account to perform certain tasks. I could not do that because the UAC elevation dialog box would appear for which the user would have to respond. To work around that problem, I came up with the solution of launching the required application from a service into the current user session with local system account privileges.This would not require us to respond to the UAC dialog box. This way I could run my application with the highest privilege possible, avoiding the UAC dialog box. In my case this was necessary since I needed to control/communicate with applications running with administrative rights without user intervention and perform some administrative tasks in the current user session on system startup.
Background
The concept which I use is very straightforward. I have a normal user mode application which communicates with a service through custom messages. On receiving the custom message, the service will launch the requested application (that I want) under the system account. To get hold of a local system account I used the winlogon's token in the required session since winlogon.exe runs under the local system account. (In Vista, services are present in session 0 and the first user logs on in session 1 and so on, unlike in XP)
Using the code
First let us review the file CustomMessageSender.cpp. This is the usermode application which communicates with the service.This application can be any normal application without any special privileges.
#define SERVICE_NAME _T("CustomSvc")
#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;
}
The code above is very simple and straightforward. I use SERVICE_USER_DEFINED_CONTROL
and SC_MANAGER_CONNECT
access permission because any user mode applications can connect to our service to send customize messages to it. No admin privileges are required. So this application sends the SERVICE_CONTROL_CUSTOM_MESSAGE
to the service. The service part of the code which receives the messages and launches the application is below. I have used a sample service posted by Anish in Code Project and added my features into it.
#define SERVICE_CONTROL_CUSTOM_MESSAGE 0x0085
BOOL LaunchAppIntoDifferentSession();
.
.
.
.
.
.
.
.
.
void WINAPI ServiceCtrlHandler(DWORD Opcode)
{
switch(Opcode)
{
case SERVICE_CONTROL_CUSTOM_MESSAGE:
LaunchAppIntoDifferentSession();
break;
In the above code snippet, I have declared the custom message and the prototype of the method which I will be executing on receiving the custom message. Let me describe the LaunchAppIntoDifferentSession
Method before I display the code snippet. To launch a process under the local system account I perform the following steps:
- Get the Active Console SessionId using
WTSGetActiveConsoleSessionId
- Since I need to launch the application under a system account, I use the token from Winlogon, since Winlogon runs under the system account. So I obtain the process ID of Winlogon and Duplicate the token.
- Then I make sure I sent the startupinfo parameter
lpDesktop
to winsta0\Default since I need to launch my process there.
- Then I use
CreateProcessAsUser
with Winlogon's duplicate token to launch my process into session 1.
- That's all. I am done.
BOOL LaunchAppIntoDifferentSession()
{
PROCESS_INFORMATION pi;
STARTUPINFO si;
BOOL bResult = FALSE;
DWORD dwSessionId,winlogonPid;
HANDLE hUserToken,hUserTokenDup,hPToken,hProcess;
DWORD dwCreationFlags;
dwSessionId = WTSGetActiveConsoleSessionId();
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)
{
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();
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;
bResult = CreateProcessAsUser(
hUserTokenDup,
_T("C:\\SessionLauncher\\a.exe"),
NULL,
NULL,
NULL,
FALSE,
dwCreationFlags,
pEnv,
NULL,
&si,
&pi
);
int iResultOfCreateProcessAsUser = GetLastError();
CloseHandle(hProcess);
CloseHandle(hUserToken);
CloseHandle(hUserTokenDup);
CloseHandle(hPToken);
return 0;
}
This way a normal user mode application can send a custom message to a service to launch itself under the local system account without the UAC dialog box popping up.
Some readers wanted to know how to:
- Get
GetUserName()
API to return the current logged on user
- Access the HKCU under the system account
Well, it is virtually impossible from the userland to surpass the UAC dialog when we launch a process which requires elevation, since it is designed that way by Microsoft. Although it may be possible to effect these changes through writing some kernel mode code (DKOM concepts). But still from the userland, the best I could think about was to impersonate the system account to act as the logged on user to access the HKCU. I use the Explorer process for this purpose since it runs under the user account.
Impersonating the user token will cause the current worker thread to run under the user context. Note that if you use CreateProcess()
it will still spawn a process under the System Account since our overall process is still running under the local system account.
The code to do that is listed in the snippet below. This code needs to be written into the application which will be launched by the service. I have not included this piece of code in the "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;
long lRegResult;
dwSessionId = WTSGetActiveConsoleSessionId();
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());
}
We need to impersonate the service token to run as a user to access the Registry. This will cause our worker thread to run in the users token's context.
ImpersonateLoggedOnUser(hPToken);
int iImpersonateResult = GetLastError();
if(iImpersonateResult == ERROR_SUCCESS)
{
GetUserName(szUserName,&dwSize);
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);
}
RevertToSelf();
I have taken a simple service posted by V.Anish to add my code to obtain the Winlogon's token and launch the application. It can be installed up typing service.exe -i
. Then you need to start the service to get it running.
If you have any doubts, you can contact me at jaisvar@gmail.com.
In my next article, I will discuss some more concepts about tweaking the UAC.