понедельник, 18 апреля 2011 г.

UAC и CreateProcessAsUser - кто кого?

Кто знает, как запустить процесс от имени другого пользователя? А если это Windows 7 с включенным UAC, а ваш пользователь имеет административные привилегии, потерять которые в запущенном процессе вы не хотите? Итак, дано: Windows 7, UAC, логин и пароль учётной записи с административными привилегиями, WinAPI и полная свобода действий. Требуется: программным путём запустить процесс от имени этого пользователя. Давайте разбираться.

Акт первый - интерактивный
Сначала вспомним, как работает UAC. При интерактивном входе с использованием административной учётной записи создаётся два токена: обычный и ограниченный. Ограниченный токен возвращается в качестве результата функции LogonUser, он же, в частности, используется системой для запуска приложений в интерактивной сессии по умолчанию. При запросе пользователя на запуск приложения в elevated-режиме, система извлекает основной токен и использует его для создания процесса. Наибольший интерес в данном случае представляет то, откуда именно извлекается основной токен. Это так называемый linked (связанный) токен, доступ к которому можно получить с помощью функции GetTokenInformation следующим образом:
HANDLE getLinkedToken(HANDLE token)
{
    TOKEN_LINKED_TOKEN linkedToken;
    DWORD retSize = 0;
    if (!::GetTokenInformation(token, TokenLinkedToken,
        &linkedToken, sizeof(linkedToken), &retSize)) {
        DWORD error = ::GetLastError();
        if (ERROR_NO_SUCH_LOGON_SESSION != error) {
            // there are no linked token: UAC disabled,
            // or standard user account is used, or ...
            return 0;
        } else if (ERROR_INVALID_PARAMETER != error) {
            // linked tokens are not supported: program is executed
            // on an old operation system
            return 0;
        } else {
            // report error
            throw std::runtime_error(formatWinError(error));
        }
    }
    if (0 == linkedToken.LinkedToken
            || INVALID_HANDLE_VALUE == linkedToken.LinkedToken) {
        // report unexpected error: linkedToken.LinkedToken
        // contains an invalid token
        throw std::runtime_error("Linked token is invalid");
    }
    return linkedToken.LinkedToken
}

Казалось бы вот оно счастье. Но любые попытки создать процесс, используя данный токен скорее всего будут завершаться с ошибкой ERROR_BAD_TOKEN_TYPE. Дело в том, что в обычной ситуации функция GetTokenInformation возвращает так называемый identification-токен (попробуйте запросить TokenImpersonationLevel этого токена с помощью той же функции GetTokenInformation). Всё, что вы можете сделать с этим токеном - это узнать его SID, прочитать в какие группы он входит и какими привилегиями обладает. Ни имперсонировать его, ни запустить с его помощью процесс, ни даже конвертировать его в основной (primary) токен с помощью функции DuplicateToken вы не сможете.

И только если у вас есть привилегия SeTcbPrivilege, linked-токен, возвращённый функцией GetTokenInfromation, оказывается основным токеном. Вот только по умолчанию этой привилегией обладает единственный пользователь в системе - Local System. И даже если вы выдадите её своему пользователю, вопреки всем рекомендациям, советующим не делать этого ни при каких условиях, она точно будет ограничена UAC`ом при интерактивном входе в систему. Единственное место, где вы имеете шанс воспользоваться подобной возможностью - это сервис, работающий под учётной записью Local System. Надо отдать должное UAC`у - даже там вам могут пригодиться подобные приседания.

Комментариев нет:

Отправить комментарий