OK, so it turns out that CreateProcessWithLogonW
function filters the user token, and so does LogonUser
. This would seem to leave us stuck, since we don't have the right privileges to correct the problem (see footnote) but it turns out that LogonUser
does not filter the token if you use LOGON32_LOGON_BATCH
rather than LOGON32_LOGON_INTERACTIVE
.
Here's some code that actually works. We use the CreateProcessAsTokenW
function to launch the process, because this particular variant requires only SE_IMPERSONATE_NAME
privilege, which is granted to administrator accounts by default.
This sample program launches a subprocess which creates a directory in c:windowssystem32
, which would not be possible if the subprocess was not elevated.
#define _WIN32_WINNT 0x0501
#include <Windows.h>
#include <Sddl.h>
#include <conio.h>
#include <stdio.h>
wchar_t command[] = L"c:\windows\system32\cmd.exe /c md c:\windows\system32\proof-that-i-am-an-admin";
int main(int argc, char **argv)
{
HANDLE usertoken;
STARTUPINFO sinfo;
PROCESS_INFORMATION pinfo;
ZeroMemory(&sinfo, sizeof(sinfo));
sinfo.cb = sizeof(sinfo);
if (!LogonUser(L"username", L"domain", L"password", LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, &usertoken))
{
printf("LogonUser: %u
", GetLastError());
return 1;
}
if (!CreateProcessWithTokenW(usertoken, LOGON_WITH_PROFILE, L"c:\windows\system32\cmd.exe", command, 0, NULL, NULL, &sinfo, &pinfo))
{
printf("CreateProcess: %u
", GetLastError());
return 1;
}
return 0;
}
However, if the target process is a GUI process (including a process with a visible console) it won't display properly. Apparently CreateProcessWithTokenW
only assigns the minimum desktop and window station permissions necessary for a process to run, which is not enough to actually display a GUI.
Even if you don't actually need to see the output, there's a risk that the broken GUI will cause functional problems with the program.
So, unless the target process runs in the background, we should probably assign permissions appropriately. In general, it is best to create a new window station and a new desktop, to isolate the target process; in this case, though, the target process is going to be running as admin anyway, so there's no point - we can make life easier by just changing the permissions on the existing window station and desktop.
Edit 24 November 2014: corrected access rights in window station ACE so they will work for non-administrative users. Note that doing this may allow the non-admin user in question to compromise processes in the target session.
#define _WIN32_WINNT 0x0501
#include <Windows.h>
#include <AccCtrl.h>
#include <Aclapi.h>
#include <stdio.h>
wchar_t command[] = L"c:\windows\system32\notepad.exe";
int main(int argc, char **argv)
{
HANDLE usertoken;
STARTUPINFO sinfo;
PROCESS_INFORMATION pinfo;
HDESK desktop;
EXPLICIT_ACCESS explicit_access;
BYTE buffer_token_user[SECURITY_MAX_SID_SIZE];
PTOKEN_USER token_user = (PTOKEN_USER)buffer_token_user;
PSECURITY_DESCRIPTOR existing_sd;
SECURITY_DESCRIPTOR new_sd;
PACL existing_dacl, new_dacl;
BOOL dacl_present, dacl_defaulted;
SECURITY_INFORMATION sec_info_dacl = DACL_SECURITY_INFORMATION;
DWORD dw, size;
HWINSTA window_station;
if (!LogonUser(L"username", L"domain", L"password", LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, &usertoken))
{
printf("LogonUser: %u
", GetLastError());
return 1;
}
if (!GetTokenInformation(usertoken, TokenUser, buffer_token_user, sizeof(buffer_token_user), &dw))
{
printf("GetTokenInformation(TokenUser): %u
", GetLastError());
return 1;
}
window_station = GetProcessWindowStation();
if (window_station == NULL)
{
printf("GetProcessWindowStation: %u
", GetLastError());
return 1;
}
if (!GetUserObjectSecurity(window_station, &sec_info_dacl, &dw, sizeof(dw), &size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
printf("GetUserObjectSecurity(window_station) call 1: %u
", GetLastError());
return 1;
}
existing_sd = malloc(size);
if (existing_sd == NULL)
{
printf("malloc failed
");
return 1;
}
if (!GetUserObjectSecurity(window_station, &sec_info_dacl, existing_sd, size, &dw))
{
printf("GetUserObjectSecurity(window_station) call 2: %u
", GetLastError());
return 1;
}
if (!GetSecurityDescriptorDacl(existing_sd, &dacl_present, &existing_dacl, &dacl_defaulted))
{
printf("GetSecurityDescriptorDacl(window_station): %u
", GetLastError());
return 1;
}
if (!dacl_present)
{
printf("no DACL present on window station
");
return 1;
}
explicit_access.grfAccessMode = SET_ACCESS;
explicit_access.grfAccessPermissions = WINSTA_ALL_ACCESS | READ_CONTROL;
explicit_access.grfInheritance = NO_INHERITANCE;
explicit_access.Trustee.pMultipleTrustee = NULL;
explicit_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
explicit_access.Trustee.TrusteeForm = TRUSTEE_IS_SID;
explicit_access.Trustee.TrusteeType = TRUSTEE_IS_USER;
explicit_access.Trustee.ptstrName = (LPTSTR)token_user->User.Sid;
dw = SetEntriesInAcl(1, &explicit_access, existing_dacl, &new_dacl);
if (dw != ERROR_SUCCESS) {
printf("SetEntriesInAcl(window_station): %u
", dw);
return 1;
}
if (!InitializeSecurityDescriptor(&new_sd, SECURITY_DESCRIPTOR_REVISION))
{
printf("InitializeSecurityDescriptor(window_station): %u
", GetLastError());
return 1;
}
if (!SetSecurityDescriptorDacl(&new_sd, TRUE, new_dacl, FALSE))
{
printf("SetSecurityDescriptorDacl(window_station): %u
", GetLastError());
return 1;
}
if (!SetUserObjectSecurity(window_station, &sec_info_dacl, &new_sd))
{
printf("SetUserObjectSecurity(window_station): %u
", GetLastError());
return 1;
}
free(existing_sd);
LocalFree(new_dacl);
desktop = GetThreadDesktop(GetCurrentThreadId());
if (desktop == NULL)
{
printf("GetThreadDesktop: %u
", GetLastError());
return 1;
}
if (!GetUserObjectSecurity(desktop, &sec_info_dacl, &dw, sizeof(dw), &size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
printf("GetUserObjectSecurity(desktop) call 1: %u
", GetLastError());
return 1;
}
existing_sd = malloc(size);
if (existing_sd == NULL)
{
printf("malloc failed
");
return 1;
}
if (!GetUserObjectSecurity(desktop, &sec_info_dacl, existing_sd, size, &dw))
{
printf("GetUserObjectSecurity(desktop) call 2: %u
", GetLastError());
return 1;
}
if (!GetUserObjectSecurity(desktop, &sec_info_dacl, existing_sd, 4096, &dw))
{
printf("GetUserObjectSecurity: %u
", GetLastError());
return 1;
}
if (!GetSecurityDescriptorDacl(existing_sd, &dacl_present, &existing_dacl, &dacl_defaulted))
{
printf("GetSecurityDescriptorDacl: %u
", GetLastError());
return 1;
}
if (!dacl_present)
{
printf("no DACL present
");
return 1;
}
explicit_access.grfAccessMode = SET_ACCESS;
explicit_access.grfAccessPermissions = GENERIC_ALL;
explicit_access.grfInheritance = NO_INHERITANCE;
explicit_access.Trustee.pMultipleTrustee = NULL;
explicit_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
explicit_access.Trustee.TrusteeForm = TRUSTEE_IS_SID;
explicit_access.Trustee.TrusteeType = TRUSTEE_IS_USER;
explicit_access.Trustee.ptstrName = (LPTSTR)token_user->User.Sid;
dw = SetEntriesInAcl(1, &explicit_access, existing_dacl, &new_dacl);
if (dw != ERROR_SUCCESS) {
printf("SetEntriesInAcl: %u
", dw);
return 1;
}
if (!InitializeSecurityDescriptor(&new_sd, SECURITY_DESCRIPTOR_REVISION))
{
printf("InitializeSecurityDescriptor: %u
", GetLastError());
return 1;
}
if (!SetSecurityDescriptorDacl(&new_sd, TRUE, new_dacl, FALSE))
{
printf("SetSecurityDescriptorDacl: %u
", GetLastError());
return 1;
}
if (!SetUserObjectSecurity(desktop, &sec_info_dacl, &new_sd))
{
printf("SetUserObjectSecurity(window_station): %u
", GetLastError());
return 1;
}
free(existing_sd);
LocalFree(new_dacl);
ZeroMemory(&sinfo, sizeof(sinfo));
sinfo.cb = sizeof(sinfo);
if (!CreateProcessWithTokenW(usertoken, LOGON_WITH_PROFILE, L"c:\windows\system32\notepad.exe", command, 0, NULL, NULL, &sinfo, &pinfo))
{
printf("CreateProcess: %u
", GetLastError());
return 1;
}
return 0;
}
Note the use of LOGON_WITH_PROFILE. This is not necessary to display a GUI, and it slows down launching the process considerably, so remove it if you don't need it - but if you are an administrator, the most likely reason that you are launching a process as a different administrator is that you need something in that administrator's user profile. (Another scenario might be that you need to use a specific domain account in order to access resources on another machine.)
Footnote:
Specifically, you need SeTcbPrivilege
in order to use GetTokenInformation
and TokenLinkedToken
to obtain a usable handle to the elevated token that LogonUser
generates. Unfortunately, this privilege is usually only available if you are running as local system.
If you do not have SeTcbPrivilege
you can still obtain a copy of the linked token, but in this case it is an impersonation token at SecurityIdentification
level so is of no use when creating a new process. Thanks to RbMm for helping me clarify this.