Неожиданно возникла проблема, что некоторые 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(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();
}
Вот реализация GetElevation() и GetElevationType():
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