CreateProcessAsUser из сервиса
От: Somescout  
Дата: 25.09.19 15:06
Оценка:
Здравствуйте.

Пытаюсь сделать виндовый сервис (C#, net core 3.0), который запускает приложения из под другого пользователя. Сервис работает из под доменного пользователя, который является администратором на этом компьютере, в Local Security Policy ему даны права Adjust Memory Quota, Act as Part of OS, Run As Service, Replace a process level token (всё это выдал когда так и не удалось запустить приложение).

При запуске консольного приложения из сервиса, Process.Start не возвращает ошибки, но запущенно приложение сразу после запуска падает вместе с conhost, не успевая дойти до кода самого приложения. Если проделывать всё это из интерактивного приложения — всё работает. Можете подсказать куда копать?

ЗЫ. Procmon ничего подозрительного не показывает.
ЗЫ2. Сейчас пробую стандартными средствами net framework запускать, но попробую и через interop.
ЗЫ3. Из под системы тоже не запускается.
ARI ARI ARI... Arrivederci!
Re: CreateProcessAsUser из сервиса
От: Somescout  
Дата: 26.09.19 05:32
Оценка:
Здравствуйте, Somescout, Вы писали:

Разобрался в чём проблема — нужно указать lpDesktop для прикрепления окна (в данном случае консоли). Выставил в "winsta0\default" — всё заработало.
ARI ARI ARI... Arrivederci!
Re[2]: CreateProcessAsUser из сервиса
От: Somescout  
Дата: 28.09.19 05:01
Оценка:
Здравствуйте, Somescout, Вы писали:

S>Разобрался в чём проблема — нужно указать lpDesktop для прикрепления окна (в данном случае консоли). Выставил в "winsta0\default" — всё заработало.


А нет, не заработало — cmd стартует, но большинство других программ вылетают с ошибкой "The application was unable to start correctly (0xc0000142)" (причём во всплывающем окне). Нет ли у кого рабочего примера, как правильно запускать процесс с использованием CreateProcessAsUser?
ARI ARI ARI... Arrivederci!
Re: CreateProcessAsUser из сервиса
От: zou  
Дата: 28.09.19 07:43
Оценка:
Здравствуйте, Somescout, Вы писали:

А в какой сессии должны приложения запускаться? И может ли быть у них UI?
Re[2]: CreateProcessAsUser из сервиса
От: Somescout  
Дата: 28.09.19 07:47
Оценка:
Здравствуйте, zou, Вы писали:

zou>А в какой сессии должны приложения запускаться? И может ли быть у них UI?


Я сейчас в отладчике (и вне его) запускаю из своей сессии (администратор, права Set Pimary Token и Adjust memory quota даны). Запускается из под имени доменного пользователя (не администратора, выдано право Logon as a batch job).

CMD.EXE запускается, но при попытке запустить, например whoami.exe или Powershell.exe — ошибка.

Я сейчас не гружу профиль, может в этом дело, но вроде это не мешало запускаться процессам, когда я использовал стандартный Process.Start() (работает через CreateProcessWithLogonW).
ARI ARI ARI... Arrivederci!
Re[3]: CreateProcessAsUser из сервиса
От: okman Беларусь https://searchinform.ru/
Дата: 28.09.19 11:59
Оценка:
Здравствуйте, Somescout, Вы писали:

S>как правильно запускать процесс с использованием CreateProcessAsUser?


А токен для CreateProcessAsUser ты как получаешь?
Re[4]: CreateProcessAsUser из сервиса
От: Somescout  
Дата: 28.09.19 15:24
Оценка:
Здравствуйте, okman, Вы писали:

O>Здравствуйте, Somescout, Вы писали:


S>>как правильно запускать процесс с использованием CreateProcessAsUser?


O>А токен для CreateProcessAsUser ты как получаешь?


        private SafeTokenHandle LoginUser(string Domain, string UserName, SecureString Password)
        {
            IntPtr passwordPtr = (Password != null) ?
                Marshal.SecureStringToGlobalAllocUnicode(Password) : IntPtr.Zero;

            try
            {
                string password = SecureStringToString(Password);
                if (Interop.Advapi32.LogonUser(UserName, Domain, passwordPtr,
                                               Interop.Advapi32.LOGON32.LOGON32_LOGON_BATCH,
                                               Interop.Advapi32.LOGON32.LOGON32_PROVIDER_DEFAULT,
                                               out var tempUserToken))
                {
                    return tempUserToken;
                }
                else
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
            }
            finally
            {
                if (passwordPtr != IntPtr.Zero)
                    Marshal.ZeroFreeGlobalAllocUnicode(passwordPtr);
            }
        }

        public int StartProcess(ProcessStartInfo processStartInfo)
        {
            using var userToken = LoginUser(processStartInfo.Domain, processStartInfo.UserName, processStartInfo.Password);

            Interop.Advapi32.STARTUPINFO startUpInfo = new Interop.Advapi32.STARTUPINFO();
            startUpInfo.cb = Marshal.SizeOf(startUpInfo);
            startUpInfo.lpDesktop = "winsta0\\default";

            Interop.Kernel32.PROCESS_INFORMATION processInfo = new Interop.Kernel32.PROCESS_INFORMATION();
            bool processStarted = Interop.Advapi32.CreateProcessAsUser(userToken, processStartInfo.FileName, processStartInfo.Arguments,
                                                             IntPtr.Zero, IntPtr.Zero, true, 0, IntPtr.Zero, null,
                                                             ref startUpInfo, out processInfo);

            if (!processStarted)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }

            Interop.Kernel32.CloseHandle(processInfo.hProcess);
            Interop.Kernel32.CloseHandle(processInfo.hThread);
            return processInfo.dwProcessId;
        }
ARI ARI ARI... Arrivederci!
Re[5]: CreateProcessAsUser из сервиса
От: okman Беларусь https://searchinform.ru/
Дата: 28.09.19 18:27
Оценка: 18 (1)
Здравствуйте, Somescout, Вы писали:

O>>А токен для CreateProcessAsUser ты как получаешь?


S>...

S>if (Interop.Advapi32.LogonUser
S>...

Когда-то решал аналогичную задачу, правда, у меня в LogonUser использовался флаг LOGON32_LOGON_INTERACTIVE.
Иногда приложение запускалось, но у главного окна почему-то отсутствовали визуальные стили (все контролы
были в стиле Windows 2000). Иногда я при запуске получал какие-то странные ошибки, например "недостаточно
памяти для завершения операции".

Пришлось копать глубже, в итоге пришел к следующим наблюдениям:

1. Если запустить процесс вручную, через "Run as different user" от имени другого пользователя, а затем
посмотреть набор SID-ов в его токене (с помощью Process Explorer или Process Hacker) и сравнить с аналогичным
набором из, например, explorer.exe (Проводник), то там видно, что у обоих процессов Logon SID совпадают,
хотя пользователи разные. То же самое наблюдается при использовании утилиты psexec (Sysinternals).

2. Если запустить процесс программно, через LogonUser -> CreateProcessAsUser, а затем снова сравнить
список SID-ов, то легко увидеть, что токен созданного процесса не содержит SID группы "Локальный вход"
(S-1-2-0), а также у него какой-то "левый" Logon SID, не соответствующий Logon SID пользователя, который
залогинен в сессии.

3. При создании сессии система назначает определенные права доступа для оконной станции, рабочего стола,
пространства имен диспетчера объектов и некоторых других объектов, принадлежащих пользователю.
При этом она указывает пользователя неявно, используя его Logon SID. Это значит, что для нормальной
работы процессу другого пользователя каким-то образом следует дать доступ к этим объектам.
Лучше всего "позаимствовать" у исходного пользователя его Logon SID — сама Windows именно так и
поступает (хотя MSDN в известном примере "Starting an Interactive Client Process in C++" делает иначе —
там меняются DACL оконной станции и рабочего стола, в них добавляются разрешения для нужного пользователя).

4. LogonUser не позволяет добавить в токен дополнительные SID-ы. Зато это могут делать функции
LsaLogonUser и LogonUserExExW.

В результате получил следующее решение:

Получаем Logon SID пользователя, залогиненного в сессии: WTSQueryUserToken, затем GetTokenInformation с
кодом TokenGroups и ищем SID с атрибутом SE_GROUP_LOGON_ID.

Вызываем LsaLogonUser или LogonUserExExW (я использовал флаг LOGON32_LOGON_INTERACTIVE),
при этом в созданный токен добавляем два SID-а: S-1-2-0 и Logon SID.

После создания токена вызываем SetTokenInformation с кодом TokenSessionId, указывая сессию, в
которой будет запущен процесс.

Загружаем профиль пользователя — LoadUserProfile.

Создаем блок переменных окружения — CreateEnvironmentBlock.

Далее уже вызывается CreateProcessAsUser...

Когда писал код, проверял на Windows 7 и Windows 8.1, пользователи — администраторы и нет, все работало норм.
Re[6]: CreateProcessAsUser из сервиса
От: Somescout  
Дата: 28.09.19 18:49
Оценка:
Здравствуйте, okman, Вы писали:

Спасибо, попробую.

O>Загружаем профиль пользователя — LoadUserProfile.


Такие вопросы:
1) Нужно дожидаться завершения дочернего процесса чтобы выгрузить профиль? Или можно как-то это обойти?
2) может ли быть незагруженный профиль причиной проблем? Вроде когда я использовал CreateProcessWithLogonW (без флага LOGON_WITH_PROFILE) — проблем не было.
ARI ARI ARI... Arrivederci!
Re[7]: CreateProcessAsUser из сервиса
От: okman Беларусь https://searchinform.ru/
Дата: 29.09.19 13:36
Оценка:
Здравствуйте, Somescout, Вы писали:

S>Такие вопросы:

S>1) Нужно дожидаться завершения дочернего процесса чтобы выгрузить профиль? Или можно как-то это обойти?
S>2) может ли быть незагруженный профиль причиной проблем? Вроде когда я использовал CreateProcessWithLogonW (без флага LOGON_WITH_PROFILE) — проблем не было.

Гм... Пересмотрел свой старый код — оказалось, что манипуляции с LogonUser/LsaLogonUser/LogonUserExExW вообще не нужны.
Вместо этого можно просто вызвать CreateProcessWithLogonW (с кодом LOGON_WITH_PROFILE или без). Ключевой момент — структуру STARTUPINFO
следует заполнить нулями, тогда функция сама проделает всю работу, необходимую для обеспечения процессу доступа к оконной
станции, рабочему столу и т.п. У нового процесса в токене есть все нужные SID-ы (и Logon SID совпадает).
А вот если самому писать в STARTUPINFO что-нибудь (например, GetStartupInfoW, lpDesktop = WinSta0\\Default и т.п.), тогда
запуск обламывается с кодом типа 0xC0000142...
Re[8]: CreateProcessAsUser из сервиса
От: Somescout  
Дата: 29.09.19 13:47
Оценка:
Здравствуйте, okman, Вы писали:

O>Гм... Пересмотрел свой старый код — оказалось, что манипуляции с LogonUser/LsaLogonUser/LogonUserExExW вообще не нужны.

O>Вместо этого можно просто вызвать CreateProcessWithLogonW (с кодом LOGON_WITH_PROFILE или без). Ключевой момент — структуру STARTUPINFO
O>следует заполнить нулями, тогда функция сама проделает всю работу, необходимую для обеспечения процессу доступа к оконной
O>станции, рабочему столу и т.п. У нового процесса в токене есть все нужные SID-ы (и Logon SID совпадает).
O>А вот если самому писать в STARTUPINFO что-нибудь (например, GetStartupInfoW, lpDesktop = WinSta0\\Default и т.п.), тогда
O>запуск обламывается с кодом типа 0xC0000142...

Дело в том, что мне вообще не нужен доступ к UI — я пытаюсь запустить консольное приложение (без создния окна), которому через перенаправление stdin нужно скормить XML с параметрами. Попытка просто запустить через Process.Start (использует как раз CreateProcessWithLogonW) из сервиса неудачна (процесс запускается, но conhost падает сразу после запуска и роняет за собой приложение). Хотя мне тут пришло в голову, что возможно надо сделать перенаправление не тослько stdin, но и out и err. Попробую завтра.
ARI ARI ARI... Arrivederci!
Re[7]: CreateProcessAsUser из сервиса
От: okman Беларусь https://searchinform.ru/
Дата: 29.09.19 18:08
Оценка: 18 (1)
Здравствуйте, Somescout, Вы писали:

S>может ли быть незагруженный профиль причиной проблем? Вроде когда я использовал CreateProcessWithLogonW (без флага LOGON_WITH_PROFILE) — проблем не было.


Если не загрузить профиль — процесс не сможет работать с пользовательским кустом реестра HKEY_CURRENT_USER.
Хотя, может, ему это и не требуется...

А если не выгрузить — данные не будут синхронизированы: не будет RegFlushKey, да и в случае использования
сетевого профиля вероятны проблемы.

S>Нужно дожидаться завершения дочернего процесса чтобы выгрузить профиль? Или можно как-то это обойти?


В общем случае, нужно дожидаться завершения всех процессов, запущенных с данным токеном, после чего выгружать профиль.
Если дочерний процесс 100% всегда один, то можно просто поставить его хэндл в ожидание, а когда процесс завершится и
хэндл перейдет в сигнальное состояние, дернуть UnloadUserProfile. Но если он может порождать другие процессы и завершаться
раньше них, тогда... я не знаю как быть В Windows нету (ну или я не нашел) механизма, чтобы подписаться на удаление
объекта logon session (функция ядра SeRegisterLogonSessionTerminatedRoutine не в счет), а это позволило бы узнать,
когда больше не осталось процессов, запущенных с данным токеном, и безопасно выгрузить профиль.

У CreateProcessWithLogon и CreateProcessWithToken есть флаг LOGON_WITH_PROFILE и в документации написано:
"The profile is unloaded after the new process and all child processes it has created are terminated".
Мне стало интересно, как сама Windows решает проблему с выгрузкой профиля. Оказалось, что на Windows 7 служба
seclogon, которая обрабатывает вызовы этих двух функций, при создании нового процесса добавляет его в Job-объект и
затем отслеживает уничтожение всего Job-а. При уничтожении, среди прочего, вызывается UnloadUserProfile.

Очень любопытно: а что будет, если дочерний процесс попытается добавить себя в другой Job-объект?
Ведь вложенные Job-объекты поддерживаются только на Windows 8 и выше...

Для себя я пока остановился на таком варианте (у меня служба запущена от LocalSystem):

1. Служба запускает в сессии пользователя промежуточный процесс: OpenProcessToken -> DuplicateTokenEx ->
SetTokenInformation(TokenSessionId) -> CreateProcessAsUser.

2. Промежуточный процесс создает с помощью LogonUserExExW токен, добавляя туда S-1-2-0 и Logon SID
пользователя: WTSQueryUserToken -> GetTokenInformation(TokenLogonId) -> LogonUserExExW -> ...

3. CreateEnvironmentBlock и т.п.

4. Далее с полученным токеном вызывается CreateProcessWithToken с флагом LOGON_WITH_PROFILE.
В STARTUPINFO.lpDesktop пишется WinSta0\Default.
Re[9]: CreateProcessAsUser из сервиса
От: okman Беларусь https://searchinform.ru/
Дата: 30.09.19 06:51
Оценка: 15 (1)
Здравствуйте, Somescout, Вы писали:

S>Дело в том, что мне вообще не нужен доступ к UI — я пытаюсь запустить консольное приложение (без создния окна)...


Дело не только в UI. Если в сессии пользователя создать приложение "не с тем" Logon SID, то оно не только UI не
сможет показывать, но и создавать/открывать объекты в пространстве имен сессии. Например:

HANDLE token;

if (LogonUserW(userName, domainName, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token))
{
    if (ImpersonateLoggedOnUser(token))
    {
        //
        // Здесь получаем ошибку, GetLastError() возвращает 5 - ERROR_ACCESS_DENIED.
        //
        HANDLE event = CreateEventW(NULL, TRUE, FALSE, L"TestEvent-123456");
Re[10]: CreateProcessAsUser из сервиса
От: Somescout  
Дата: 30.09.19 08:12
Оценка:
Здравствуйте, okman, Вы писали:

O>Здравствуйте, Somescout, Вы писали:


S>>Дело в том, что мне вообще не нужен доступ к UI — я пытаюсь запустить консольное приложение (без создния окна)...


O>Дело не только в UI. Если в сессии пользователя создать приложение "не с тем" Logon SID, то оно не только UI не

O>сможет показывать, но и создавать/открывать объекты в пространстве имен сессии. Например:

O>
O>HANDLE token;

O>if (LogonUserW(userName, domainName, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token))
O>{
O>    if (ImpersonateLoggedOnUser(token))
O>    {
O>        //
O>        // Здесь получаем ошибку, GetLastError() возвращает 5 - ERROR_ACCESS_DENIED.
O>        //
O>        HANDLE event = CreateEventW(NULL, TRUE, FALSE, L"TestEvent-123456"); 
O>


Нашёл вот такое решение (фактически упакованная в C# классическая статья про запуск интерактивных процессов): https://stackoverflow.com/questions/677874/starting-a-process-with-credentials-from-a-windows-service/30687230#30687230

То есть делает то же самое — пользователю, от которого делается запуск, даёт доступ к WinStation текущего приложения. Проверил — работает, и из сервиса, и из консоли (даже из под системного аккаунта работает).

ЗЫ. Огромное спасибо за помощь!
ARI ARI ARI... Arrivederci!
Отредактировано 30.09.2019 8:17 Somescout . Предыдущая версия . Еще …
Отредактировано 30.09.2019 8:13 Somescout . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.