We are writing a run-only remote desktop application, that uses SendInput for keyboard (& mouse) interaction. However it cannot interact with UAC prompts.
What permissions/rights does our application require for this?
Background info: The application is spawned by another process duplicating winlogon.exe's Access Token. This enables to run under SYSTEM account with System Integrity Level, is attached to the Physical Console Session and has the same SE privileges as winlogon.exe (https://learn.microsoft.com/en-us/windows/desktop/secauthz/privilege-constants), although not all of them are enabled.
struct MY_TOKEN_PRIVILEGES {
DWORD PrivilegeCount;
LUID_AND_ATTRIBUTES Privileges[2];
};
int RunUnderWinLogon(LPCWSTR executableWithSendInput)
{
DWORD physicalConsoleSessionId = WTSGetActiveConsoleSessionId();
auto winlogonPid = GetWinLogonPid(); // external function
if (!winlogonPid)
{
std::cout << "ERROR getting winlogon pid" << std::endl;
return 0;
}
HANDLE hWinlogonToken, hProcess;
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, &hWinlogonToken))
{
printf("Process token open Error: %u\n", GetLastError());
}
// Is security descriptor needed for SendInput?
PSECURITY_DESCRIPTOR pSD = NULL;
SECURITY_ATTRIBUTES saToken;
ZeroMemory(&saToken, sizeof(SECURITY_ATTRIBUTES));
saToken.nLength = sizeof (SECURITY_ATTRIBUTES);
saToken.lpSecurityDescriptor = pSD;
saToken.bInheritHandle = FALSE;
HANDLE hWinlogonTokenDup;
if (!DuplicateTokenEx(hWinlogonToken, TOKEN_ALL_ACCESS, &saToken, SecurityImpersonation, TokenPrimary, &hWinlogonTokenDup))
{
printf("DuplicateTokenEx Error: %u\n", GetLastError());
}
if (!SetTokenInformation(hWinlogonTokenDup, TokenSessionId, (void*)physicalConsoleSessionId, sizeof(DWORD)))
{
printf("SetTokenInformation Error: %u\n", GetLastError());
}
//Adjust Token privilege
LUID luidSeDebugName;
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luidSeDebugName))
{
printf("Lookup Privilege value Error: %u\n", GetLastError());
}
LUID luidSeTcbName;
if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &luidSeTcbName))
{
printf("Lookup Privilege value Error: %u\n", GetLastError());
}
MY_TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 2;
tp.Privileges[0].Luid = luidSeDebugName;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
tp.Privileges[1].Luid = luidSeTcbName;
tp.Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hWinlogonTokenDup, FALSE, (PTOKEN_PRIVILEGES)&tp, /*BufferLength*/0, /*PreviousState*/(PTOKEN_PRIVILEGES)NULL, NULL))
{
printf("Adjust Privilege value Error: %u\n", GetLastError());
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
printf("Token does not have the privilege\n");
}
DWORD creationFlags;
creationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
LPVOID pEnv = NULL;
if (CreateEnvironmentBlock(&pEnv, hWinlogonTokenDup, TRUE))
{
std::cout << "CreateEnvironmentBlock() success" << std::endl;
creationFlags |= CREATE_UNICODE_ENVIRONMENT;
}
SECURITY_ATTRIBUTES saProcess, saThread;
ZeroMemory(&saProcess, sizeof(SECURITY_ATTRIBUTES));
ZeroMemory(&saThread, sizeof(SECURITY_ATTRIBUTES));
saProcess.nLength = sizeof (SECURITY_ATTRIBUTES);
saProcess.lpSecurityDescriptor = pSD;
saProcess.bInheritHandle = FALSE;
saThread.nLength = sizeof (SECURITY_ATTRIBUTES);
saThread.lpSecurityDescriptor = pSD;
saThread.bInheritHandle = FALSE;
STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = (LPWSTR)L"winsta0\\default";
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
BOOL bResult = CreateProcessAsUser(
hWinlogonTokenDup, // client's access token
executableWithSendInput, // file using SendInput
NULL, // command line
&saProcess, // pointer to process SECURITY_ATTRIBUTES
&saThread, // pointer to thread SECURITY_ATTRIBUTES
FALSE, // handles are not inheritable
creationFlags, // creation flags
pEnv, // pointer to new environment block
NULL, // name of current directory
&si, // pointer to STARTUPINFO structure
&pi // receives information about new process
);
}
SendInput
, like SendMessage and PostMessage is limited to work between processes in the same login session and within the same desktop as the target process. The UAC prompt is shown in the Winlogon's Secure Desktop (winsta0\Winlogon
) so you need poll the current desktop periodically with OpenInputDesktop()
then use SetThreadDesktop()
to enable the current thread to send messages to the user's desktop / secure desktop, whichever active is.
In case of UAC, you need to run your process under the System Account, to comply with the UIPI Integrity Level check, as you already did.
See also: How to switch a process between default desktop and Winlogon desktop?