Сбой SendInput при запросе UAC

Мы пишем приложение для удаленного рабочего стола, предназначенное только для выполнения, которое использует SendInput для взаимодействия с клавиатурой (и мышью). Однако он не может взаимодействовать с подсказками UAC.

Какие разрешения / права требуются для этого нашему приложению?

Справочная информация: приложение создается другим процессом, дублирующим токен доступа winlogon.exe. Это позволяет работать под учетной записью SYSTEM с уровнем целостности системы, привязано к сеансу физической консоли и имеет те же привилегии SE, что и winlogon.exe (https://docs.microsoft.com/en-us/windows/desktop/secauthz/Privilege-constants), хотя не все из них включены.

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
  );
}

person Norbert    schedule 14.06.2019    source источник
comment
Вы можете изучить, как это реализовано в удаленных решениях с открытым исходным кодом.   -  person David Heffernan    schedule 14.06.2019
comment
Вы должны воспроизвести ту же проблему с привилегированными окнами. Например, если вы щелкните правой кнопкой мыши cmd.exe, а затем выберите «Запуск от имени администратора», вы не сможете отправлять данные в такое окно.   -  person Bemipefe    schedule 01.08.2019


Ответы (2)


SendInput, как и SendMessage и PostMessage, ограничивается работой между процессами в одном сеансе входа в систему и на том же рабочем столе, что и целевой процесс. Подсказка UAC отображается в Защищенный рабочий стол Winlogon (winsta0\Winlogon), поэтому вам нужно периодически опрашивать текущий рабочий стол с помощью OpenInputDesktop(), а затем использовать SetThreadDesktop(), чтобы разрешить текущему потоку отправлять сообщения на рабочий стол пользователя / защищенный рабочий стол, в зависимости от того, какой из них активен.

В случае UAC вам необходимо запустить процесс под системной учетной записью в соответствии с проверкой уровня целостности UIPI., как вы это уже сделали.

См. также: Как переключить процесс между режимами по умолчанию рабочий стол и рабочий стол Winlogon?

person Lorlin    schedule 17.06.2019
comment
Можно ли разрешить процессу, запущенному от имени обычного пользователя, отправлять входные данные в привилегированные окна? (например, приглашение UAC или окно, принадлежащее привилегированному пользователю) Может быть, что-то вроде двоичной подписи, белого списка ... и т. д. может быть полезно? - person Bemipefe; 01.08.2019

Вы можете авторизовать ваше приложение для выполнения этих задач UIAutomation / screen reader.

Создайте запись в манифесте сборки, которая включает:

uiAccess="true"

Затем вы должны поставить цифровую подпись с помощью действующего цифрового сертификата.
И вы должны быть установлены в Program Files.

Возможность автоматизировать диалог UAC - серьезное дело; и вы не можете дурачиться с этим волей-неволей.

Бонусное чтение

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

person Ian Boyd    schedule 16.03.2020