Неожиданно возникла проблема, что некоторые API в Windows 8 работают только из под non-elevated пользователя и только когда UAC включен. Соответственно встал вопрос как прикинуться non-elevated пользователем, если программа запущена под elevated пользователем. Сразу скажу, мы не знаем пользователький пароль.
К сожалению выяснилось, что поменять elevation у процесса нельзя. Можно только запустить новый с новыми elevation при помощи CreatePrcoesssAsUser() или CreatePrcoesssWithToken().
Были исследованы просторы интернета и к великому удивлению количество неработающих примеров зашкалило. Я думаю виной тому появление UAC в Windows Vista и то, что работало до перестало работать после:
- Использование linked token не работает так как этот token имеет SECURITY_IMPERSONATION_LEVEL равным SecurityIdentification, что недостаточно для заруска дочернего процесса. Тебуется как минимум SecurityImpersonation.
- Использование CreateRestrictedToken() не работает так как не меняется TokenElevationType. Наверное это бага в Windows, но он остается TokenElevationTypeFull вместо ожидаемого TokenElevationTypeLimited.
bool RerunAsReducedUserIfNeed(int * const pExitCode) { DWORD elevationFlags; if (RtlQueryElevationFlags(&elevationFlags) != ERROR_SUCCESS) throw std::exception("Can't get elevation flags"); if ((elevationFlags & ELEVATION_UAC_ENABLED) == 0) throw std::exception("UAC have to be enabled for metro-style applications"); HandleHolder itselfToken; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &itselfToken)) throw _system_error("Can't open process token", GetLastError()); if (GetElevationType(itselfToken.GetHandle()) != TokenElevationTypeFull) return false; HandleHolder const reducedUserHandle(GetReducedUserHandle()); if (GetElevationType(reducedUserHandle.GetHandle()) == TokenElevationTypeFull) throw std::exception("Token type is still full"); if (GetElevation(reducedUserHandle.GetHandle())) throw std::exception("Token is still elevated"); STARTUPINFO si; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); PROCESS_INFORMATION pi; if (!CreateProcessWithTokenW(reducedUserHandle.GetHandle(), 0, nullptr, GetCommandLineW(), 0, nullptr, nullptr, &si, &pi)) throw _system_error("Can't create child proccess", GetLastError()); HandleHolder thread(pi.hThread); HandleHolder process(pi.hProcess); if (WaitForSingleObject(process.GetHandle(), INFINITE) != WAIT_OBJECT_0) throw std::exception("Failed to wait child process to be finished"); if (pExitCode != nullptr) if (!GetExitCodeProcess(process.GetHandle(), reinterpret_castВот реализация GetElevation() и GetElevationType():(pExitCode))) throw _system_error("Can't get exit code for child process", GetLastError()); return true; } static HANDLE GetReducedUserHandle() { HWND const wnd = GetShellWindow(); if (wnd == nullptr) throw std::exception("Can't find shell window"); DWORD pid; GetWindowThreadProcessId(wnd, &pid); HandleHolder const shellProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); if (shellProcess.IsClosed()) throw _system_error("Can't open shell process", GetLastError()); HandleHolder shellToken; if (!OpenProcessToken(shellProcess.GetHandle(), TOKEN_DUPLICATE | TOKEN_QUERY, &shellToken)) throw _system_error("Can't open shell process token", GetLastError()); HandleHolder reducedUserHandle; if (!DuplicateTokenEx(shellToken.GetHandle(), MAXIMUM_ALLOWED, nullptr, SecurityImpersonation, TokenPrimary, &reducedUserHandle)) throw _system_error("Can't duplicate shell token", GetLastError()); return reducedUserHandle.Detach(); }
static bool GetElevation(HANDLE const token) { DWORD size; TOKEN_ELEVATION te; if (!GetTokenInformation(token, TokenElevation, &te, sizeof(te), &size)) throw _system_error("Can't get token elevation", GetLastError()); return te.TokenIsElevated != FALSE; } static TOKEN_ELEVATION_TYPE GetElevationType(HANDLE const token) { DWORD size; TOKEN_ELEVATION_TYPE tet; if (!GetTokenInformation(token, TokenElevationType, &tet, sizeof(tet), &size)) throw _system_error("Can't get token elevation type", GetLastError()); return tet; }Вот реализация HandleHolder и _system_error для дотошных:
struct HandleHolder { HandleHolder(); HandleHolder(HANDLE handle); ~HandleHolder(); void Close(); operator HANDLE() const { return m_Handle; } HANDLE * operator &() { Close(); return &m_Handle; } bool operator==(HANDLE const value) const { return m_Handle == value; } bool operator!=(HANDLE const value) const { return m_Handle != value; } bool operator==(HandleHolder const & value) const { return m_Handle == value.m_Handle; } bool operator!=(HandleHolder const & value) const { return m_Handle != value.m_Handle; } void Attach(HANDLE const handle) { Close(); m_Handle = handle; } HANDLE Detach() { HANDLE const handle = m_Handle; m_Handle = NULL; return handle; } private: HANDLE m_Handle; // Note: This object can't be copied HandleHolder(HandleHolder const & other); HandleHolder & operator=(HandleHolder const & other); }; HandleHolder::HandleHolder() : m_Handle(NULL) { } HandleHolder::HandleHolder(HANDLE const handle) : m_Handle(handle) { } HandleHolder::~HandleHolder() { if (m_Handle != NULL) { CloseHandle(m_Handle); m_Handle = NULL; } } void HandleHolder::Close() { if (m_Handle != NULL) { if (!CloseHandle(m_Handle)) throw _system_error("Can't close handle", GetLastError()); m_Handle = NULL; } } struct _system_error : public std::exception { _system_error(LPCSTR _What, DWORD _SystemError); DWORD const SystemError; }; _system_error::_system_error(LPCSTR const _What, DWORD const _SystemError) : std::exception(_What), SystemError(_SystemError) { }
P.S. Критика только приветствуется!
P.P.S. Обнаружена интересная особенность вышеприведенного кода. Когда из под обычного пользователя не администратора запускается процесс администратора с полными правами, а из него метод RerunAsReducedUserIfNeed(), то GetShellWindow() возвращает HWND обычного пользователся!
No comments:
Post a Comment