3

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
  );
}
Norbert
  • 41
  • 3
  • You can study how open source remote solutions implement this – David Heffernan Jun 14 '19 at 09:50
  • You should reproduce the same problem with "priviledged windows". For example if you right click on cmd.exe and then click on "Run as Administrator" you can't send inputs to such window. – Bemipefe Aug 01 '19 at 17:19

2 Answers2

5

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?

Community
  • 1
  • 1
Lorlin
  • 844
  • 5
  • 15
  • Is it possible to "authorize" a process running as a normal user to send inputs to priviledged windows ? (for example an UAC prompt or a window owned by a priviledged user ) Maybe something like binary signature, white listing ... etc... might it be helpful ? – Bemipefe Aug 01 '19 at 17:22
0

It is possible to authorize your application to be able to do these UIAutomation/screen reader tasks.

Create an entry in your assembly manifest that includes:

uiAccess="true"

Then you have to be digitally sign with a valid digital certificate.
And you have to be installed in Program Files.

Being able to automate the UAC dialog is serious business; and you don't get to screw around with that willy-nilly.

Bonus Reading

https://techcommunity.microsoft.com/t5/windows-blog-archive/using-the-uiaccess-attribute-of-requestedexecutionlevel-to/ba-p/228641

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219