Начало процесса UAC с неинтерактивным сервисом (win32/.net/powershell)

Я использую стороннюю службу Windows, которая обрабатывает некоторые задачи автоматизации, запуская скрипты и исполняемые файлы с помощью CreateProcessAsUser(). Я столкнулся с проблемами в Windows Server 2008 из-за UAC, и как возвышение LUA обрабатывается через API.

Служба работает как LocalSystem и не имеет "Interact With Desktop". Эти процессы запускаются как пользователи в группе "Администраторы", но не в учетной записи "Администратор" (которая освобождается от многих ограничений UAC). Все настройки UAC по умолчанию.

Я могу передать произвольные команды или код powershell службе, но я не могу "вырваться" из невысокого, неинтерактивного процесса, который запускается службой.

Суть проблемы заключается в том, что единственным (общедоступным) API-интерфейсом для запуска повышенного процесса является ShellExecute() с глаголом "runas", но насколько я могу сказать, что нельзя вызывать из неинтерактивный сервис, или вы получаете такие ошибки, как "Для этой операции требуется интерактивная оконная станция".

Единственное обходное решение, которое я нашел, упоминается здесь: http://www.eggheadcafe.com/software/aspnet/29620442/how-to-proper-use-sendinp.aspx

В Vista официальный документированный способ для подъема процесса используется только shell API ShellExecute (Ex) (не CreateProcess или CreateProcessAsUser). Поэтому ваше приложение должно ShellExecute (Ex) для запуска помощника для вызова SendInput. Кроме того, из-за Сессии 0 изоляции, услуга может использовать только CreateProcessAsUser или CreateProcessWithLogonW (нельзя использовать ShellExecute (Ex)), чтобы указать интерактивный рабочий стол.

.. Я думаю, что нет прямого способа порождать повышенный процесс с окна. Мы можем только сначала использовать CreateProcessAsUser или CreateProcessWithLogonW, чтобы создать невыполненный процесс в пользователе сеанс (интерактивный рабочий стол). Затем в невозбужденный процесс, он может использовать ShellExecute (Ex), чтобы создать процесс для реальной задачи.

Чтобы сделать это из кода .net/powershell, похоже, что мне нужно будет сделать некоторые сложные материалы P/Invoke для вызова CreateProcessAsUser или CreateProcessWithLogonW, поскольку .Net System.Diagnostics.ProcessStartInfo не имеет эквивалента lpDesktop что я могу установить "winsta0\default". И я не понимаю, есть ли у LocalSystem права называть CreateProcessAsUser или CreateProcessWithLogonW.

Я также посмотрел http://blogs.msdn.com/alejacma/archive/2007/12/20/how-to-call-createprocesswithlogonw-createprocessasuser-in-net.aspx а также Process.Start с разными учетными данными с UAC в

Исходя из всего этого, я пришел к выводу, что нет простого способа сделать это. Я что-то упускаю? Это действительно не похоже, что это должно быть так сложно. Похоже, что UAC никогда не был разработан для обработки неинтерактивных случаев использования.

И если кто-то из людей Microsoft в конечном итоге прочитает это, я заметил, что способ, которым ShellExecute внутренне обрабатывает возвышение, - это обратиться в Службу информации о приложениях (AIS). Почему не один и тот же вызов AIS доступен через некоторые API Win32 или .NET? http://msdn.microsoft.com/en-us/library/bb756945.aspx

Извините, что немного поработал. Спасибо за любые идеи.

Ответ 1

"Официальный" способ отключить нулевую сессию - использовать комбинацию API терминальных служб и CreateProcessAsUser() для запуска процесса в пользовательском сеансе. На моей старой работе мы сделали именно это, так как нам нужно было показать диалог пользователю с сервисом до установки загруженного обновления. Итак, я знаю, что он работает, по крайней мере, на WinXP, Win2K3, Vista и Win7, но я не ожидайте, что Win 2K8 будет слишком отличаться. В основном, процесс выглядит следующим образом:

  • Вызовите WTSGetActiveConsoleSessionId(), чтобы получить активный идентификатор сеанса консоли (ОЧЕНЬ важно, поскольку интерактивный сеанс НЕ всегда сеанс 1, даже в клиентских системах). Этот API также вернет -1, если в интерактивный сеанс не зарегистрирован активный пользователь (то есть локально зарегистрирован на физическом компьютере, в отличие от использования RDP).
  • Передайте идентификатор сеанса из предыдущего вызова API в WTSQueryUserToken(), чтобы получить открытый токен, который перепечатывает пользователя, зарегистрированного в консоли.
  • Вызвать DuplicateTokenEx(), чтобы преобразовать токен олицетворения (от WTSQueryUserToken) в первичный токен.
  • Вызовите CreateEnvironmentBlock(), чтобы создать новую среду для процесса (необязательно, но если вы этого не сделаете, у процесса не будет).
  • Передайте первичный токен с шага № 3 в вызов CreateProccessAsUser() вместе с командной строкой для исполняемого файла. Если вы создали блок среды с шага №4, вы должны также передать флаг CREATE_UNICODE_ENVIRONMENT (всегда). Это может показаться глупым, но API терпит неудачу, если вы этого не сделаете (с ERROR_INVALID_PARAMTER).
  • Если вы создали блок среды, тогда вам нужно вызвать DestroyEnvironmentBlock, иначе вы создадите утечку памяти. При запуске процесс получает отдельную копию блока среды, поэтому вы уничтожаете только локальные данные.

И вуаля! Windows делает некоторую внутреннюю магию, и вы видите запуск приложения. Однако, хотя это приведет к запуску и интерактивному процессу со службы, я не уверен, что он обойдет UAC (но не цитируйте меня на этом). Другими словами, он может не запускаться в качестве повышенного процесса, если только реестр или внутренний манифест не говорят об этом, и даже тогда вы все равно можете получить приглашение UAC. Если токен, который вы получаете с шага 3, является ограниченным токеном, вы можете использовать AdjustTokenPrivileges() для восстановления повышенного (полного) токена, но не цитируйте меня на этом. Однако, как указано в документах MSDN, обратите внимание, что невозможно "добавить" привилегии на токен, который еще не имеет их (например, вы не можете превратить токен ограниченного пользователя в администратора с помощью AdjustTokenPrivileges; основной пользователь должен был бы быть администратором для начала).

Технически это можно сделать с Win2K вперед. Однако это действительно возможно только с WinXP, так как Win2K не имеет API WTSGetActiveConsoleSessionId() и WTSQueryUserToken() (наряду с WTSEnumerateProcesses() для Win2K Pro). Вы можете записать код 0 в качестве идентификатора сеанса (так как это всегда имеет место в Win2K), и, я полагаю, вы могли бы получить токен пользователя, перечислив запущенные процессы и дублируя один из своих токенов (он должен быть таким, который имеет интерактивный SID). Независимо от того, что CreateProcessAsUser() будет вести себя одинаково при передаче интерактивного токена пользователя, даже если вы не выбираете "Взаимодействовать с рабочим столом" из настроек службы. Это также более безопасно, чем запуск непосредственно из службы в любом случае, поскольку процесс не наследует божественный токен доступа LocalSystem.

Теперь я не знаю, выполнит ли ваше стороннее приложение это при запуске script/process, но если вы хотите сделать это из службы, вот как (и с Vista или Win7, это единственный способ преодолеть изоляцию сеанса 0).

Ответ 2

В зависимости от вашего варианта использования вы можете делать то, что я делаю. Я выслеживаю процесс winlogon для активного сеанса и кражу его токена. Если нет активного сеанса (возвращается API -1), используйте 1, если WINVER >= 6 в противном случае 0. Это приведет к SYSTEM в активном сеансе.