Загрузка профиля пользователя
От: Sharpeye Россия  
Дата: 04.05.17 16:54
Оценка:
Привет.

Есть сервис, который запускает процесс в сессии пользователя UserA от имени пользователя UserB (оба пользователя доменные).
Для логона пользователя зову LogonUserExExW, а для загрузки профиля — LoadUserProfile:
LogonUserExExWPtr(
            user_name,
            domain,
            password,
            LOGON32_LOGON_INTERACTIVE,
            LOGON32_PROVIDER_DEFAULT,
            token_groups,
            &token,
            nullptr,
            nullptr,
            nullptr,
            nullptr
        );

PROFILEINFO profile = {};
profile.dwSize = sizeof( profile );
profile.lpUserName = user_name;
profile.dwFlags = PI_NOUI;

LoadUserProfile( token, &profile );

Процесс запускается, но для профиля пользователя UserB создается папка TEMP, а не UserB.
При этом, если запускать через PSExec, создается директория с правильным именем.
Re: Загрузка профиля пользователя
От: okman Беларусь https://searchinform.ru/
Дата: 04.05.17 18:37
Оценка:
Здравствуйте, Sharpeye, Вы писали:

S>Привет.


S>...

S>Процесс запускается, но для профиля пользователя UserB создается папка TEMP, а не UserB.
S>При этом, если запускать через PSExec, создается директория с правильным именем.

Переменные окружения созданного процесса посмотри (можно с помощью Process Explorer, например).
А перед CreateProcessXxx надо еще CreateEnvironmentBlock вызвать...
Re[2]: Загрузка профиля пользователя
От: Sharpeye Россия  
Дата: 04.05.17 18:52
Оценка:
Здравствуйте, okman, Вы писали:

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


S>>Привет.


S>>...

S>>Процесс запускается, но для профиля пользователя UserB создается папка TEMP, а не UserB.
S>>При этом, если запускать через PSExec, создается директория с правильным именем.

O>Переменные окружения созданного процесса посмотри (можно с помощью Process Explorer, например).

O>А перед CreateProcessXxx надо еще CreateEnvironmentBlock вызвать...
Да, CreateEnvironmentBlock я вызываю, но проблемы возникают еще на стадии загрузки профиля, т.к. до вызова LoadUserProfile это:
 ExpandEnvironmentStringsForUserW( token, L"%USERPROFILE%", p, n )

возвращает C:\Users\Default, а GetUserProfileDirectory завершится с ERROR_FILE_NOT_FOUND.
После же обе вернут D:\Profiles\TEMP (вместо D:\Profiles\UserB).
Re: Загрузка профиля пользователя
От: ononim  
Дата: 04.05.17 20:13
Оценка:
S>Есть сервис, который запускает процесс в сессии пользователя UserA от имени пользователя UserB (оба пользователя доменные).
S>Для логона пользователя зову LogonUserExExW, а для загрузки профиля — LoadUserProfile:
1) А UserB случайно уже не залогинен? Боюсь винда не сможет сделать два одновременных логона в один и тот же профиль.
2) А если вместо LoadUserProfile/CreateProcessAsUser заюзать CreateProcessWithTokenW(..LOGON_WITH_PROFILE..)
Как много веселых ребят, и все делают велосипед...
Re[2]: Загрузка профиля пользователя
От: Sharpeye Россия  
Дата: 05.05.17 10:25
Оценка:
Здравствуйте, ononim, Вы писали:

S>>Есть сервис, который запускает процесс в сессии пользователя UserA от имени пользователя UserB (оба пользователя доменные).

S>>Для логона пользователя зову LogonUserExExW, а для загрузки профиля — LoadUserProfile:
O>1) А UserB случайно уже не залогинен? Боюсь винда не сможет сделать два одновременных логона в один и тот же профиль.
UserB логинится только сервисом и только раз.
O>2) А если вместо LoadUserProfile/CreateProcessAsUser заюзать CreateProcessWithTokenW(..LOGON_WITH_PROFILE..)
В этом случае создается правильная папка, но процесс завершается с 0xC0000142 (Faulting module path: USER32.dll).
Создание процесса такое:
STARTUPINFOW info {};
info.cb = sizeof( info );
info.lpDesktop = L"WinSta0\\Default";

PROCESS_INFORMATION pi {};

CreateProcessWithTokenW( token, LOGON_WITH_PROFILE, nullptr, cmdline, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, nullptr, nullptr, &info, &pi );
Re[3]: Загрузка профиля пользователя
От: ononim  
Дата: 05.05.17 12:30
Оценка: 4 (1)
O>>2) А если вместо LoadUserProfile/CreateProcessAsUser заюзать CreateProcessWithTokenW(..LOGON_WITH_PROFILE..)
S>В этом случае создается правильная папка, но процесс завершается с 0xC0000142 (Faulting module path: USER32.dll).
Ну, давайте использовать серое вещество для логики, а не только как СУБД. user32 — это окна и прочая мишура, связанная с пользовательской сессией. Если user32 не смог ее проинициализировать — видимо не смог получить к чемуто доступ. Процессу на старте прежде всего нужен доступ к оконной станции (которая WinSta0) ну и к десктопу (Default). Вот пример. Интересно правда, почему оно вообще работало раньше, с неправильным профилем.

Но полагаю на этом ваши приключения не закончатся, т.к. токен унаследовал session-id вашего сервиса, то есть нулевую. А интерактивной является некоторая пользовательская сессия, то есть — ненулевая (какая именно — тема для отдельного обсуждения). Поменять session id в токене прежде чем юзать его для запуска не просто, а очень просто — SetTokenInformation(TokenSessionId). Но самое интересное как проставить описанные выше права в объекты, находящиеся в той другой сессии, если это потребуется.
Как много веселых ребят, и все делают велосипед...
Re: Загрузка профиля пользователя
От: rumit7  
Дата: 05.05.17 12:58
Оценка:
Здравствуйте, Sharpeye, Вы писали:

Ваш случай описан хорошо здесь см. секцию 1.B.:

1.B. ) Your service is running in LocalSystem account and you are willing to launch the application as some user other than interactively logged on user.

For an interactive application we need two essential things

1) An access token with right session ID

a. You need an access token of that user. You would need to call LogonUser to get the primary token. This call will create a new session and associate the session ID with token. Now, your token does not have the right Session ID. You can change it by calling SetTokenInformation. After this call you have the right session ID in the token.

2) You need a Window station and desktop to associate with your application.

Newly created application will not have access right to the interactive session’s window station and desktop. This application cannot associate itself with interactive desktop.

Therefore, even though we have good token the launched process will not survive. It will be killed by Windows desktop manager because the desktop it is trying to access is not accessible to it.

You can change the permissions of the Window station and desktop. The only way to change the permission is to get the handle of the desktop. The process cannot obtain the handle of the desktop of session other than their session. This implies that service which is running in session 0 cannot obtain the handle of the desktop in the interactive session.

To workaround this you would need to perform following steps

a) Launch a hidden temporary process as explained in step 1.A.

I. Get the user token by calling WTSQueryUserToken (WTSGetActiveConsoleSessionId (), &hToken) ;

II. Use this token in CreateProcessAsUser to launch the temporary process.

Call CreateProcessWithLogonW from the temporary process to create the target application. CreateProcessWithLogonW saves you from explicitly modifying the DACL of the interactive desktop. However, you need the credentials of the target user to provide in this API.

Re[4]: Загрузка профиля пользователя
От: Sharpeye Россия  
Дата: 05.05.17 16:36
Оценка:
Здравствуйте, ononim, Вы писали:

O>>>2) А если вместо LoadUserProfile/CreateProcessAsUser заюзать CreateProcessWithTokenW(..LOGON_WITH_PROFILE..)

S>>В этом случае создается правильная папка, но процесс завершается с 0xC0000142 (Faulting module path: USER32.dll).
O>Ну, давайте использовать серое вещество для логики, а не только как СУБД.
Дельный совет.
O>user32 — это окна и прочая мишура, связанная с пользовательской сессией. Если user32 не смог ее проинициализировать — видимо не смог получить к чемуто доступ. Процессу на старте прежде всего нужен доступ к оконной станции (которая WinSta0) ну и к десктопу (Default). Вот пример. Интересно правда, почему оно вообще работало раньше, с неправильным профилем.

O>Но полагаю на этом ваши приключения не закончатся, т.к. токен унаследовал session-id вашего сервиса, то есть нулевую. А интерактивной является некоторая пользовательская сессия, то есть — ненулевая (какая именно — тема для отдельного обсуждения). Поменять session id в токене прежде чем юзать его для запуска не просто, а очень просто — SetTokenInformation(TokenSessionId). Но самое интересное как проставить описанные выше права в объекты, находящиеся в той другой сессии, если это потребуется.

Подозреваю, что LogonUserExExW как раз и была добавлена для того, чтобы избежать лишних приключений.

Оке, приведу примерный код запуска (надо было бы сразу, да):

enable_TCB_NAME();

HANDLE logged_user{};
WTSQueryUserToken( session_id, &logged_user );

auto token_groups = get_logon_sid( logged_user );

auto token = logon_user_ex_ex_w( name, domain, password, token_groups );

auto user_name_v = to_vector( domain + L"\\" + name );

PROFILEINFO profile = {};
profile.dwSize = sizeof( profile );
profile.lpUserName = user_name_v.data();
profile.dwFlags = PI_NOUI;

LoadUserProfile( token, &profile );

SetTokenInformation( token, TokenSessionId, &session_id, sizeof( session_id ) );

STARTUPINFOW info{};

info.cb = sizeof( info );
info.lpDesktop = L"WinSta0\\Default";

PROCESS_INFORMATION pi{};

LPVOID environment = nullptr;
CreateEnvironmentBlock( &environment, token, FALSE );

WCHAR cmdline[] = L"calc.exe";

CreateProcessAsUserW( token, nullptr
    , cmdline
    , nullptr, nullptr
    , FALSE
    , CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, environment, nullptr, &info, &pi );

В результате получаю запущенный процесс в сессии UserA, но профиль грузится в D:\Profiles\TEMP.
Проблема наблюдается после LoadUserProfile — переменная окружения USERPROFILE уже указывает не туда.

Сравнил в Process Explorer вкладку Security — списки групп у процесса, который запускает мой сервис, и процесса от psexec совпадают.
Re[2]: Загрузка профиля пользователя
От: Sharpeye Россия  
Дата: 05.05.17 16:43
Оценка:
Здравствуйте, rumit7, Вы писали:

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


R>Ваш случай описан хорошо здесь см. секцию 1.B.

Ага, спасибо, я видел это, но надеюсь, что таки получится запускать процесс через LogonUserExExW+CreateProcessAsUserW.
Re[5]: Загрузка профиля пользователя
От: ononim  
Дата: 05.05.17 17:01
Оценка:
может дело в этом?

Services and applications that call LoadUserProfile should check to see if the user has a roaming profile. If the user has a roaming profile, specify its path as the lpProfilePath member of PROFILEINFO. To retrieve the user's roaming profile path, you can call the NetUserGetInfo function, specifying information level 3 or 4.

(с) https://msdn.microsoft.com/en-us/library/windows/desktop/bb762281(v=vs.85).aspx
Как много веселых ребят, и все делают велосипед...
Re[3]: Загрузка профиля пользователя
От: rumit7  
Дата: 05.05.17 17:43
Оценка:
Здравствуйте, Sharpeye, Вы писали:

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


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


R>>Ваш случай описан хорошо здесь см. секцию 1.B.

S>Ага, спасибо, я видел это, но надеюсь, что таки получится запускать процесс через LogonUserExExW+CreateProcessAsUserW.

если сможете правильно дать привелегии на десктоп.. у меня в быстром варианте не получилось
Re[5]: Загрузка профиля пользователя
От: rumit7  
Дата: 05.05.17 17:59
Оценка:
Здравствуйте, Sharpeye, Вы писали:

S>CreateProcessAsUserW( token, nullptr

S> , cmdline
S> , nullptr, nullptr
S> , FALSE
S> , CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, environment, nullptr, &info, &pi );
S>[/ccode]
S>В результате получаю запущенный процесс в сессии UserA, но профиль грузится в D:\Profiles\TEMP.

может как в примере здесь получить current directory path через GetUserProfileDirectory?
Re[6]: Загрузка профиля пользователя
От: Sharpeye Россия  
Дата: 05.05.17 19:47
Оценка:
Здравствуйте, ononim, Вы писали:

O>может дело в этом?

O>

O>Services and applications that call LoadUserProfile should check to see if the user has a roaming profile. If the user has a roaming profile, specify its path as the lpProfilePath member of PROFILEINFO. To retrieve the user's roaming profile path, you can call the NetUserGetInfo function, specifying information level 3 or 4.

O>(с) https://msdn.microsoft.com/en-us/library/windows/desktop/bb762281(v=vs.85).aspx

NetUserGetInfo показывает, что нет у пользователя roaming profile (пустое поле usri4_profile).
Прицепился Windbg к psexecsvc: в структуре PROFILEINFO заполняются только dwSize и lpUserName.
Да и вообще, windbg показывает в psexecsvc вроде бы всё то же, что и в моем сервисе, но вот путь в результате получается другой
Re[7]: Загрузка профиля пользователя
От: ononim  
Дата: 05.05.17 20:57
Оценка:
S>NetUserGetInfo показывает, что нет у пользователя roaming profile (пустое поле usri4_profile).
S>Прицепился Windbg к psexecsvc: в структуре PROFILEINFO заполняются только dwSize и lpUserName.
Еще момент есть с токенами и процессами тонкий, не знаю правда может ли вызвать такие проблемы и не уверен подвержена ли ему ета LogonUserExExW. Момент состоит в том, что токен и процесс — сам по себе объект ядра, защищенный DACL'ом. И когда они создаются — они получают себе овнером — того кто их создает, и дефолтовый SD/DACL разрешает доступ только этому овнеру. И ежели токен был создан system'ом, то в результате доступ к нему имеет только SYSTEM. А тот юзер который этот токен представляет — фиг. На практике ето вылазит в виде забавного access denied'а от OpenProcessToken(GetCurrentProcess() вызванного из процесса, который был стартован с таким токеном. Аналогичный эффект имеет место с процессами. Вызывая CreateProcessAsUserW с нулевыми lpProcessAttributes/lpThreadAttributes — создаются объекты процесса и потока, владельцами которых является не юзер, под которым их запустили, а юзер, который их запустил, то есть — систем. Это в свою очередь выражается в том что OpenProcess(GetCurrentProcessId() и OpenThread(GetCurrentThreadId) такого процесса говорит access denied

Лечиться все созданием security descriptor'a, который разрешает доступ к объекту данному юзеру и созданием токена и процесса/потока с испольованием этого SD в аргументах SECURITY_ATTRIBUTES:
DuplicateTokenEx(token, MAXIMUM_ALLOWED, lpTokenAttributes, SecurityIdentification, TokenPrimary, &new_token);
LoadUserProfile(..new_token..)
CreateProcessAsUserW(new_token, .. lpProcessAttribute, lpThreadAttributes.. )


Можно попробовать проще — проимперсонейтив нужного юзера:
ImpersonateLoggedOnUser(token)
DuplicateTokenEx(token, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &new_token);
LoadUserProfile(..new_token..)
CreateProcessAsUserW(new_token, .. NULL, NULL.. )
RevertToSelf()

логика такая, что объекты получают SD согласно _эффективному_ токену. Так что если проимперсонировать юзера и вызвать DuplicateTokenEx — то новый токен будет создан с SD, разрешающему доступ этому юзеру. Но боюсь что CreateProcessAsUserW обломается обнаружив отсутствие SE_ASSIGN_PRIMARY_TOKEN у вызывающего потока. А может додумается проверить привилегию у процесса и не обломается. Ваш код — вам и ковыряться..


S>Да и вообще, windbg показывает в psexecsvc вроде бы всё то же, что и в моем сервисе, но вот путь в результате получается другой

Посмотрите
1) Не висит ли имперсонейшен токен на потоке на момент вызова ключевых функций командой !token
2) Загляните в указанные SECURITY_ATTRIBUTES всякие CreateProcess* DuplicateToken*. Тут поможет команда !sd
Как много веселых ребят, и все делают велосипед...
Отредактировано 05.05.2017 21:06 ononim . Предыдущая версия . Еще …
Отредактировано 05.05.2017 21:02 ononim . Предыдущая версия .
Отредактировано 05.05.2017 21:00 ononim . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.