From 94ae6a34b0cb90ee387c6e9a86ccd1998f8864d6 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Thu, 15 Sep 2016 19:32:24 +0300 Subject: initial commit --- CMakeLists.txt | 10 ++++ LICENSE.txt | 21 +++++++ error.h | 26 ++++++++ handle.h | 64 ++++++++++++++++++++ main.cpp | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.rc | Bin 0 -> 5844 bytes os.h | 30 ++++++++++ process.h | 40 +++++++++++++ resource_ids.h | Bin 0 -> 1528 bytes sid.h | 68 +++++++++++++++++++++ token.h | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 617 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 LICENSE.txt create mode 100644 error.h create mode 100644 handle.h create mode 100644 main.cpp create mode 100644 main.rc create mode 100644 os.h create mode 100644 process.h create mode 100644 resource_ids.h create mode 100644 sid.h create mode 100644 token.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..aff5426 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +project(privilege_test) + +file(GLOB ${PROJECT_NAME}_source_files "*.cpp") +file(GLOB ${PROJECT_NAME}_header_files "*.h") +file(GLOB ${PROJECT_NAME}_resource_files "*.rc") +add_executable(${PROJECT_NAME} WIN32 + ${${PROJECT_NAME}_source_files} + ${${PROJECT_NAME}_header_files} + ${${PROJECT_NAME}_resource_files}) +target_compile_definitions(${PROJECT_NAME} PRIVATE _UNICODE) diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..80c5e0a --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Egor Tensin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/error.h b/error.h new file mode 100644 index 0000000..2ea7f85 --- /dev/null +++ b/error.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include + +typedef std::system_error Error; + +namespace error +{ + inline void raise(const char* function_name) + { + const auto ec = GetLastError(); + throw std::system_error(ec, std::system_category(), function_name); + } + + void report(const Error& e) + { + MessageBoxA(NULL, e.what(), NULL, MB_OK); + } + + int get_code(const Error& e) + { + return e.code().value(); + } +} diff --git a/handle.h b/handle.h new file mode 100644 index 0000000..5f83f24 --- /dev/null +++ b/handle.h @@ -0,0 +1,64 @@ +#pragma once + +#include + +#include + +#include +#include + +class Handle +{ +public: + Handle() = default; + + explicit Handle(HANDLE raw) + : impl{raw} + { } + + Handle(Handle&& other) noexcept + { + swap(other); + } + + Handle& operator=(Handle other) noexcept + { + swap(other); + return *this; + } + + void swap(Handle& other) noexcept + { + using std::swap; + swap(impl, other.impl); + } + + operator HANDLE() const + { + return impl.get(); + } + +private: + struct Close + { + void operator()(HANDLE raw) const + { + if (raw == NULL || raw == INVALID_HANDLE_VALUE) + return; + const auto ret = CloseHandle(raw); + assert(ret); + } + }; + + std::unique_ptr impl; + + Handle(const Handle&) = delete; +}; + +namespace std +{ + void swap(Handle& a, Handle& b) noexcept + { + a.swap(b); + } +} diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..322626c --- /dev/null +++ b/main.cpp @@ -0,0 +1,183 @@ +#include "error.h" +#include "os.h" +#include "process.h" +#include "resource_ids.h" +#include "sid.h" +#include "token.h" + +#include +#include +#include + +#include + +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") + +bool is_user_in_administrators() +{ + const auto token = token::impersonate( + token::get_linked( + token::open_for_current_process(token::permissions::query() | token::permissions::duplicate()))); + + return token::belongs(token, sid::builtin_administrators()); +} + +bool is_run_as_administrator() +{ + return token::belongs(token::dumb(), sid::builtin_administrators()); +} + +bool is_elevated() +{ + return token::is_elevated(token::open_for_current_process()); +} + +void set_label(HWND root, int id, bool val) +{ + const auto label = GetDlgItem(root, id); + SetWindowTextW(label, val ? L"True" : L"False"); +} + +void set_label(HWND root, int id, const std::wstring& s) +{ + const auto label = GetDlgItem(root, id); + SetWindowTextW(label, s.c_str()); +} + +BOOL on_init_dialog(HWND wnd, HWND, LPARAM) +{ + try + { + set_label(wnd, IDC_STATIC_IS_ADMIN, is_user_in_administrators()); + } + catch (const Error& e) + { + set_label(wnd, IDC_STATIC_IS_ADMIN, L"N/A"); + error::report(e); + } + + try + { + set_label(wnd, IDC_STATIC_IS_RUN_AS_ADMIN, is_run_as_administrator()); + } + catch (const Error& e) + { + set_label(wnd, IDC_STATIC_IS_RUN_AS_ADMIN, L"N/A"); + error::report(e); + } + + if (os::is_vista_or_later()) + { + try + { + const auto elevated = is_elevated(); + set_label(wnd, IDC_STATIC_IS_ELEVATED, elevated); + + const auto elevate_button = GetDlgItem(wnd, IDC_BUTTON_ELEVATE); + Button_SetElevationRequiredState(elevate_button, !elevated); + } + catch (const Error& e) + { + set_label(wnd, IDC_STATIC_IS_ELEVATED, L"N/A"); + error::report(e); + } + + try + { + set_label(wnd, IDC_STATIC_INTEGRITY_LEVEL, token::integrity_level_to_string( + token::query_integrity_level(token::open_for_current_process()))); + } + catch (const Error& e) + { + set_label(wnd, IDC_STATIC_INTEGRITY_LEVEL, L"N/A"); + error::report(e); + } + } + else + { + set_label(wnd, IDC_STATIC_IS_ELEVATED, L"N/A"); + set_label(wnd, IDC_STATIC_INTEGRITY_LEVEL, L"N/A"); + } + + return TRUE; +} + +void on_command(HWND wnd, int id, HWND, unsigned int) +{ + switch (id) + { + case IDC_BUTTON_ELEVATE: + { + bool as_admin = false; + try + { + as_admin = is_run_as_administrator(); + } + catch (const Error& e) + { + error::report(e); + break; + } + + if (as_admin) + { + MessageBoxW(wnd, L"Already elevated!", L"Elevation", MB_OK); + break; + } + + try + { + process::runas(process::get_executable_path()); + } + catch (const Error& e) + { + if (error::get_code(e) != ERROR_CANCELLED) + error::report(e); + break; + } + + EndDialog(wnd, 1); + break; + } + + case IDOK: + case IDCANCEL: + EndDialog(wnd, 0); + break; + } +} + +void on_close(HWND wnd) +{ + EndDialog(wnd, 0); +} + +INT_PTR CALLBACK dialog_main( + HWND wnd, + UINT msg, + WPARAM wParam, + LPARAM lParam) +{ + switch (msg) + { + HANDLE_MSG(wnd, WM_INITDIALOG, on_init_dialog); + HANDLE_MSG(wnd, WM_COMMAND, on_command); + HANDLE_MSG(wnd, WM_CLOSE, on_close); + + default: + return FALSE; + } +} + +int APIENTRY wWinMain( + HINSTANCE instance, + HINSTANCE, + wchar_t*, + int) +{ + return static_cast(DialogBox( + instance, + MAKEINTRESOURCE(IDD_MAINDIALOG), + NULL, + dialog_main)); +} diff --git a/main.rc b/main.rc new file mode 100644 index 0000000..5468c6e Binary files /dev/null and b/main.rc differ diff --git a/os.h b/os.h new file mode 100644 index 0000000..e4ba71c --- /dev/null +++ b/os.h @@ -0,0 +1,30 @@ +#pragma once + +#include "error.h" + +#include + +namespace os +{ + OSVERSIONINFOW get_version_info() + { + OSVERSIONINFOW info; + ZeroMemory(&info, sizeof(info)); + info.dwOSVersionInfoSize = sizeof(info); + + if (!GetVersionExW(&info)) + error::raise("GetVersionExW"); + + return info; + } + + bool is_vista_or_later(const OSVERSIONINFOW& info) + { + return info.dwMajorVersion >= 6; + } + + bool is_vista_or_later() + { + return is_vista_or_later(get_version_info()); + } +} diff --git a/process.h b/process.h new file mode 100644 index 0000000..9aaf319 --- /dev/null +++ b/process.h @@ -0,0 +1,40 @@ +#pragma once + +#include "error.h" + +#include +#include + +#include +#include + +namespace process +{ + std::wstring get_executable_path() + { + static constexpr DWORD max_path = MAX_PATH; + + std::array buf; + + const auto ret = GetModuleFileNameW(NULL, buf.data(), max_path); + + if (GetLastError() != ERROR_SUCCESS) + error::raise("GetModuleFileNameW"); + + return buf.data(); + } + + void runas(const std::wstring& exe_path, HWND hwnd = NULL, int nShow = SW_NORMAL) + { + SHELLEXECUTEINFOW info; + ZeroMemory(&info, sizeof(info)); + info.cbSize = sizeof(info); + info.lpVerb = L"runas"; + info.lpFile = exe_path.c_str(); + info.hwnd = hwnd; + info.nShow = nShow; + + if (!ShellExecuteExW(&info)) + error::raise("ShellExecuteExW"); + } +} diff --git a/resource_ids.h b/resource_ids.h new file mode 100644 index 0000000..54718a8 Binary files /dev/null and b/resource_ids.h differ diff --git a/sid.h b/sid.h new file mode 100644 index 0000000..5da8da7 --- /dev/null +++ b/sid.h @@ -0,0 +1,68 @@ +#pragma once + +#include "error.h" + +#include +#include + +#include +#include +#include + +constexpr DWORD max_sid_size = SECURITY_MAX_SID_SIZE; +typedef std::array SidBuffer; + +namespace sid +{ + SidBuffer well_known(WELL_KNOWN_SID_TYPE type) + { + SidBuffer buffer; + DWORD cb = static_cast(buffer.size()); + + if (!CreateWellKnownSid(type, NULL, buffer.data(), &cb)) + error::raise("CreateWellKnownSid"); + + return buffer; + } + + SidBuffer builtin_administrators() + { + /* + void* sid = nullptr; + SID_IDENTIFIER_AUTHORITY authority = SECURITY_NT_AUTHORITY; + + if (!AllocateAndInitializeSid( + &authority, + 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &sid)) + { + error::raise("AllocateAndInitializeSid"); + } + + return std::unique_ptr{sid}; + */ + + return well_known(WinBuiltinAdministratorsSid); + } + + struct DeleteSidString + { + void operator()(wchar_t* s) const + { + LocalFree(s); + } + }; + + std::wstring to_string(const SidBuffer& sid) + { + wchar_t* s = nullptr; + + if (!ConvertSidToStringSidW(const_cast(sid.data()), &s)) + error::raise("ConvertSidToStringSidW"); + + return std::unique_ptr{s}.get(); + } +} diff --git a/token.h b/token.h new file mode 100644 index 0000000..66dac28 --- /dev/null +++ b/token.h @@ -0,0 +1,175 @@ +#pragma once + +#include "error.h" +#include "handle.h" +#include "os.h" +#include "sid.h" + +#include + +#include +#include +#include +#include + +namespace token +{ + namespace permissions + { + constexpr DWORD query() { return TOKEN_QUERY; } + + constexpr DWORD duplicate() { return TOKEN_DUPLICATE; } + } + + constexpr DWORD default_permissions = permissions::query(); + + // Current thread's primary impersonation token. + Handle dumb() { return Handle{NULL}; } + + Handle open_for_process( + const Handle& process, + DWORD permissions = default_permissions) + { + HANDLE raw; + + if (!OpenProcessToken(process, permissions, &raw)) + error::raise("OpenProcessToken"); + + return Handle{raw}; + } + + Handle open_for_current_process( + DWORD permissions = default_permissions) + { + return open_for_process(Handle{GetCurrentProcess()}, permissions); + } + + Handle get_linked(Handle&& token) + { + if (!os::is_vista_or_later()) + return std::move(token); + + auto type = TokenElevationTypeDefault; + DWORD cb = 0; + + if (!GetTokenInformation(token, TokenElevationType, &type, sizeof(type), &cb)) + error::raise("GetTokenInformation"); + + if (type != TokenElevationTypeLimited) + return std::move(token); + + HANDLE raw; + + if (!GetTokenInformation(token, TokenLinkedToken, &raw, sizeof(raw), &cb)) + error::raise("GetTokenInformation"); + + return Handle{raw}; + } + + Handle impersonate(const Handle& token) + { + HANDLE raw; + + if (!DuplicateToken(token, SecurityIdentification, &raw)) + error::raise("DuplicateToken"); + + return Handle{raw}; + } + + // + // FUNCTION: query_integrity_level() + // + // RETURN VALUE: Returns the integrity level of the current process. It is + // usually one of these values: + // + // SECURITY_MANDATORY_UNTRUSTED_RID (SID: S-1-16-0x0) + // Means untrusted level. It is used by processes started by the + // Anonymous group. Blocks most write access. + // + // SECURITY_MANDATORY_LOW_RID (SID: S-1-16-0x1000) + // Means low integrity level. It is used by Protected Mode Internet + // Explorer. Blocks write acess to most objects (such as files and + // registry keys) on the system. + // + // SECURITY_MANDATORY_MEDIUM_RID (SID: S-1-16-0x2000) + // Means medium integrity level. It is used by normal applications + // being launched while UAC is enabled. + // + // SECURITY_MANDATORY_HIGH_RID (SID: S-1-16-0x3000) + // Means high integrity level. It is used by administrative applications + // launched through elevation when UAC is enabled, or normal + // applications if UAC is disabled and the user is an administrator. + // + // SECURITY_MANDATORY_SYSTEM_RID (SID: S-1-16-0x4000) + // Means system integrity level. It is used by services and other + // system-level applications (such as Wininit, Winlogon, Smss, etc.) + // + DWORD query_integrity_level(const Handle& token) + { + DWORD cb = 0; + + if (!GetTokenInformation(token, TokenIntegrityLevel, NULL, 0, &cb)) + { + switch (GetLastError()) + { + case ERROR_INSUFFICIENT_BUFFER: + break; + default: + error::raise("GetTokenInformation"); + } + } + + std::vector buf(cb); + const auto token_level = reinterpret_cast(buf.data()); + + if (!GetTokenInformation(token, TokenIntegrityLevel, token_level, cb, &cb)) + error::raise("GetTokenInformation"); + + // Integrity Level SIDs are in the form of S-1-16-0xXXXX. (e.g. + // S-1-16-0x1000 stands for low integrity level SID). There is one and + // only one subauthority. + return *GetSidSubAuthority(token_level->Label.Sid, 0); + } + + std::wstring integrity_level_to_string(DWORD level) + { + static const std::unordered_map names = + { + {SECURITY_MANDATORY_UNTRUSTED_RID, L"Untrusted"}, + {SECURITY_MANDATORY_LOW_RID, L"Low"}, + {SECURITY_MANDATORY_MEDIUM_RID, L"Medium"}, + {SECURITY_MANDATORY_HIGH_RID, L"High"}, + {SECURITY_MANDATORY_SYSTEM_RID, L"System"}, + }; + + static constexpr auto unknown_name = L"Unknown"; + + const auto it = names.find(level); + + if (it == names.cend()) + return unknown_name; + + return it->second; + } + + bool belongs(const Handle& token, const SidBuffer& sid) + { + BOOL b = FALSE; + + if (!CheckTokenMembership(token, const_cast(sid.data()), &b)) + error::raise("CheckTokenMembership"); + + return b != FALSE; + } + + bool is_elevated(const Handle& token) + { + TOKEN_ELEVATION elevation; + DWORD cb = 0; + + if (!GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &cb)) + error::raise("GetTokenInformation"); + + return elevation.TokenIsElevated != 0; + } +} -- cgit v1.2.3