diff --git a/src/windhawk/app/functions.cpp b/src/windhawk/app/functions.cpp index 0b2f437..9e6af93 100644 --- a/src/windhawk/app/functions.cpp +++ b/src/windhawk/app/functions.cpp @@ -2,6 +2,8 @@ #include "functions.h" +#include "resource.h" + namespace Functions { namespace { @@ -215,6 +217,12 @@ BOOL IsRunAsAdmin() { PCWSTR LoadStrFromRsrc(UINT uStrId) { PCWSTR pStr; if (!LoadString(nullptr, uStrId, (WCHAR*)&pStr, 0)) { + if (uStrId == IDS_TRAY_ADMIN_CMD) { + return L"Command Prompt (Admin)"; + } + if (uStrId == IDS_TRAY_OPEN_ADMIN) { + return L"Open Windhawk (Admin)"; + } pStr = L"(Could not load resource)"; } diff --git a/src/windhawk/app/main_window.cpp b/src/windhawk/app/main_window.cpp index 93803ba..8c1b54e 100644 --- a/src/windhawk/app/main_window.cpp +++ b/src/windhawk/app/main_window.cpp @@ -5,6 +5,7 @@ #include "functions.h" #include "logger.h" #include "resource.h" +#include "service.h" #include "session_metadata.h" #include "ui_control.h" #include "version.h" @@ -508,9 +509,11 @@ LRESULT CMainWindow::OnTrayIcon(UINT uMsg, WPARAM wParam, LPARAM lParam) { enum class Action { kNone, kOpenUI, + kOpenUIAsAdmin, kOpenUpdatePage, kModTaskManager, kToolkit, + kOpenAdminCmd, kExit, }; @@ -522,6 +525,9 @@ LRESULT CMainWindow::OnTrayIcon(UINT uMsg, WPARAM wParam, LPARAM lParam) { menu.AppendMenu(MF_STRING, static_cast(Action::kOpenUI), Functions::LoadStrFromRsrc(IDS_TRAY_OPEN)); + menu.AppendMenu(MF_STRING, + static_cast(Action::kOpenUIAsAdmin), + Functions::LoadStrFromRsrc(IDS_TRAY_OPEN_ADMIN)); menu.AppendMenu(MF_SEPARATOR); menu.AppendMenu(MF_STRING, static_cast(Action::kModTaskManager), @@ -531,6 +537,9 @@ LRESULT CMainWindow::OnTrayIcon(UINT uMsg, WPARAM wParam, LPARAM lParam) { (std::wstring(Functions::LoadStrFromRsrc(IDS_TRAY_TOOLKIT)) + (m_disableToolkitHotkey ? L"" : L"\tCtrl+Win+W")) .c_str()); + menu.AppendMenu(MF_STRING, + static_cast(Action::kOpenAdminCmd), + Functions::LoadStrFromRsrc(IDS_TRAY_ADMIN_CMD)); menu.AppendMenu(MF_SEPARATOR); menu.AppendMenu(MF_STRING, static_cast(Action::kExit), Functions::LoadStrFromRsrc(IDS_TRAY_EXIT)); @@ -570,6 +579,10 @@ LRESULT CMainWindow::OnTrayIcon(UINT uMsg, WPARAM wParam, LPARAM lParam) { RunUI(); break; + case Action::kOpenUIAsAdmin: + RunUIAsAdmin(); + break; + case Action::kOpenUpdatePage: OpenUpdatePage(); break; @@ -582,6 +595,10 @@ LRESULT CMainWindow::OnTrayIcon(UINT uMsg, WPARAM wParam, LPARAM lParam) { ShowToolkitDialog(); break; + case Action::kOpenAdminCmd: + OpenAdminCmd(); + break; + case Action::kExit: if (m_portable) { Exit(); @@ -1015,6 +1032,22 @@ void CMainWindow::StopService(HWND hWnd) { &bVerificationFlagChecked); } +void CMainWindow::OpenAdminCmd() { + if (m_portable) { + ShellExecute(m_hWnd, L"runas", L"cmd.exe", nullptr, nullptr, + SW_SHOWNORMAL); + return; + } + + try { + Service::LaunchAdminCmd(); + } catch (const std::exception& e) { + LOG(L"Failed to launch admin CMD via service: %S", e.what()); + ShellExecute(m_hWnd, L"runas", L"cmd.exe", nullptr, nullptr, + SW_SHOWNORMAL); + } +} + void CMainWindow::RunUI(HWND hWnd) { if (!hWnd) { hWnd = m_hWnd; @@ -1028,6 +1061,32 @@ void CMainWindow::RunUI(HWND hWnd) { } } +void CMainWindow::RunUIAsAdmin(HWND hWnd) { + if (!hWnd) { + hWnd = m_hWnd; + } + + if (m_portable) { + auto modulePath = wil::GetModuleFileName(); + auto uiExePath = + std::filesystem::path(modulePath).parent_path() / L"windhawk-ui.exe"; + ShellExecute(hWnd, L"runas", uiExePath.c_str(), nullptr, nullptr, + SW_SHOWNORMAL); + return; + } + + try { + Service::LaunchAdminUI(); + } catch (const std::exception& e) { + LOG(L"Failed to launch admin UI via service: %S", e.what()); + auto modulePath = wil::GetModuleFileName(); + auto uiExePath = + std::filesystem::path(modulePath).parent_path() / L"windhawk-ui.exe"; + ShellExecute(hWnd, L"runas", uiExePath.c_str(), nullptr, nullptr, + SW_SHOWNORMAL); + } +} + void CMainWindow::CloseUI() { try { UIControl::CloseUI(); diff --git a/src/windhawk/app/main_window.h b/src/windhawk/app/main_window.h index 93e1090..27976df 100644 --- a/src/windhawk/app/main_window.h +++ b/src/windhawk/app/main_window.h @@ -88,6 +88,7 @@ class CMainWindow : public CWindowImpl, void Exit(); void StopService(HWND hWnd = nullptr); void RunUI(HWND hWnd = nullptr); + void RunUIAsAdmin(HWND hWnd = nullptr); void CloseUI(); UINT GetNextUpdateDelay(ULONGLONG lastUpdateCheck); void SetLastUpdateTime(); @@ -95,6 +96,7 @@ class CMainWindow : public CWindowImpl, void OpenUpdatePage(); void ShowLoadedModsDialog(); void ShowToolkitDialog(bool triggeredBySystemInstability = false); + void OpenAdminCmd(); void SwitchToSafeMode(); void HandleExplorerCrash(int explorerCrashCount); diff --git a/src/windhawk/app/resource.h b/src/windhawk/app/resource.h index 5955760..6e56b12 100644 --- a/src/windhawk/app/resource.h +++ b/src/windhawk/app/resource.h @@ -9,7 +9,9 @@ #define IDD_TOOLKIT 0x84 #define IDS_TRAY_OPEN 0x90 #define IDS_TRAY_LOADED_MODS 0x91 +#define IDS_TRAY_OPEN_ADMIN 0x92 #define IDS_TRAY_TOOLKIT 0xA0 +#define IDS_TRAY_ADMIN_CMD 0xA1 #define IDS_TRAY_EXIT 0xB0 #define IDS_EXITDLG_TITLE 0xB1 #define IDS_EXITDLG_CONTENT 0xB2 diff --git a/src/windhawk/app/rsrc.rc b/src/windhawk/app/rsrc.rc index 3fc89ac..03df398 100644 Binary files a/src/windhawk/app/rsrc.rc and b/src/windhawk/app/rsrc.rc differ diff --git a/src/windhawk/app/service.cpp b/src/windhawk/app/service.cpp index 20eed89..0ac3d51 100644 --- a/src/windhawk/app/service.cpp +++ b/src/windhawk/app/service.cpp @@ -90,6 +90,94 @@ void CreateProcessOnSessionId(DWORD dwSessionId, nullptr, &startupInfo, &processInfo)); } +void CreateAdminProcessOnSessionId(DWORD dwSessionId, + const WCHAR* pszPath, + WCHAR* pszCommandLine) { + wil::unique_handle userToken; + THROW_IF_WIN32_BOOL_FALSE(WTSQueryUserToken(dwSessionId, &userToken)); + + HANDLE hTargetToken = userToken.get(); + wil::unique_handle linkedTokenHandle; + + TOKEN_LINKED_TOKEN linkedToken = {}; + DWORD returnLength = 0; + if (GetTokenInformation(userToken.get(), TokenLinkedToken, &linkedToken, + sizeof(linkedToken), &returnLength) && + linkedToken.LinkedToken) { + linkedTokenHandle.reset(linkedToken.LinkedToken); + hTargetToken = linkedTokenHandle.get(); + } + + wil::unique_environment_block environment; + THROW_IF_WIN32_BOOL_FALSE( + CreateEnvironmentBlock(&environment, hTargetToken, FALSE)); + + wil::unique_process_information processInfo; + STARTUPINFO startupInfo = {sizeof(STARTUPINFO)}; + startupInfo.lpDesktop = const_cast(L"WinSta0\\Default"); + + THROW_IF_WIN32_BOOL_FALSE(CreateProcessAsUser( + hTargetToken, pszPath, pszCommandLine, nullptr, nullptr, FALSE, + NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, environment.get(), + nullptr, &startupInfo, &processInfo)); +} + +void LaunchAdminCmdOnActiveSession() { + DWORD sessionId = WTSGetActiveConsoleSessionId(); + if (sessionId == 0xFFFFFFFF) { + WTS_SESSION_INFO* sessionInfo = nullptr; + DWORD dwCount = 0; + if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &sessionInfo, + &dwCount)) { + wil::unique_wtsmem_ptr scopedSessionInfo( + sessionInfo); + for (DWORD i = 0; i < dwCount; i++) { + if (sessionInfo[i].State == WTSActive) { + sessionId = sessionInfo[i].SessionId; + break; + } + } + } + } + + if (sessionId == 0xFFFFFFFF) { + THROW_WIN32_MSG(ERROR_NOT_FOUND, "No active interactive session found"); + } + + WCHAR cmdPath[] = L"C:\\Windows\\System32\\cmd.exe"; + CreateAdminProcessOnSessionId(sessionId, cmdPath, nullptr); +} + +void LaunchAdminUIOnActiveSession() { + DWORD sessionId = WTSGetActiveConsoleSessionId(); + if (sessionId == 0xFFFFFFFF) { + WTS_SESSION_INFO* sessionInfo = nullptr; + DWORD dwCount = 0; + if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &sessionInfo, + &dwCount)) { + wil::unique_wtsmem_ptr scopedSessionInfo( + sessionInfo); + for (DWORD i = 0; i < dwCount; i++) { + if (sessionInfo[i].State == WTSActive) { + sessionId = sessionInfo[i].SessionId; + break; + } + } + } + } + + if (sessionId == 0xFFFFFFFF) { + THROW_WIN32_MSG(ERROR_NOT_FOUND, "No active interactive session found"); + } + + auto modulePath = wil::GetModuleFileName(); + auto uiExePath = + std::filesystem::path(modulePath).parent_path() / L"windhawk-ui.exe"; + std::wstring commandLine = L"\"" + uiExePath.native() + L"\""; + CreateAdminProcessOnSessionId(sessionId, uiExePath.c_str(), + commandLine.data()); +} + // Launches the Windhawk daemon on every logged-on session, each on that // session's own user token. A daemon opens the UI unless started with // -tray-only, so the session named by runUiSessionId gets one without it, which @@ -176,6 +264,8 @@ class ServiceInstance { wil::unique_event m_svcScanForProcessesEvent; wil::unique_event m_svcEmergencyStopEvent; wil::unique_event m_svcSafeModeStopEvent; + wil::unique_event m_svcLaunchAdminCmdEvent; + wil::unique_event m_svcLaunchAdminUIEvent; std::optional m_engineControl; }; @@ -274,6 +364,12 @@ VOID ServiceInstance::SvcInit(DWORD dwArgc, LPTSTR* lpszArgv) { m_svcSafeModeStopEvent.reset(Functions::CreateEventForMediumIntegrity( ServiceCommon::kSafeModeStopEventName, TRUE)); + m_svcLaunchAdminCmdEvent.reset(Functions::CreateEventForMediumIntegrity( + ServiceCommon::kLaunchAdminCmdEventName, FALSE)); + + m_svcLaunchAdminUIEvent.reset(Functions::CreateEventForMediumIntegrity( + ServiceCommon::kLaunchAdminUIEventName, FALSE)); + auto settings = StorageManager::GetInstance().GetAppConfig(L"Settings", false); @@ -301,6 +397,8 @@ VOID ServiceInstance::SvcRun(DWORD dwArgc, LPTSTR* lpszArgv) { m_svcScanForProcessesEvent.get(), m_svcEmergencyStopEvent.get(), m_svcSafeModeStopEvent.get(), + m_svcLaunchAdminCmdEvent.get(), + m_svcLaunchAdminUIEvent.get(), }; while (true) { @@ -344,6 +442,28 @@ VOID ServiceInstance::SvcRun(DWORD dwArgc, LPTSTR* lpszArgv) { break; } + case WAIT_OBJECT_0 + 4: { + VERBOSE(L"Received launch admin CMD event"); + keepLooping = true; + try { + LaunchAdminCmdOnActiveSession(); + } catch (const std::exception& e) { + LOG(L"Failed to launch admin CMD: %S", e.what()); + } + break; + } + + case WAIT_OBJECT_0 + 5: { + VERBOSE(L"Received launch admin UI event"); + keepLooping = true; + try { + LaunchAdminUIOnActiveSession(); + } catch (const std::exception& e) { + LOG(L"Failed to launch admin UI: %S", e.what()); + } + break; + } + default: LOG(L"Received unknown event %u", dwWaitResult); break; @@ -461,6 +581,15 @@ DWORD ServiceInstance::SvcCtrlHandlerEx(DWORD dwControl, } return NO_ERROR; + case ServiceCommon::kControlLaunchAdminCmd: + VERBOSE("Handling SERVICE_CONTROL_LAUNCH_ADMIN_CMD"); + try { + LaunchAdminCmdOnActiveSession(); + } catch (const std::exception& e) { + LOG(L"Failed to launch admin CMD: %S", e.what()); + } + return NO_ERROR; + case SERVICE_CONTROL_INTERROGATE: return NO_ERROR; @@ -652,4 +781,56 @@ void Stop(bool disableAutoStart) { } } +void LaunchAdminCmd() { + wil::unique_event event( + OpenEvent(EVENT_MODIFY_STATE, FALSE, + ServiceCommon::kLaunchAdminCmdEventName)); + if (event) { + SetEvent(event.get()); + return; + } + + wil::unique_schandle scManager( + OpenSCManager(nullptr, // local computer + nullptr, // ServicesActive database + 0)); + THROW_LAST_ERROR_IF_NULL(scManager); + + wil::unique_schandle service( + OpenService(scManager.get(), ServiceCommon::kName, + SERVICE_USER_DEFINED_CONTROL)); + THROW_LAST_ERROR_IF_NULL(service); + + SERVICE_STATUS serviceStatus; + THROW_IF_WIN32_BOOL_FALSE( + ControlService(service.get(), ServiceCommon::kControlLaunchAdminCmd, + &serviceStatus)); +} + +void LaunchAdminUI() { + wil::unique_event event( + OpenEvent(EVENT_MODIFY_STATE, FALSE, + ServiceCommon::kLaunchAdminUIEventName)); + if (event) { + SetEvent(event.get()); + return; + } + + wil::unique_schandle scManager( + OpenSCManager(nullptr, // local computer + nullptr, // ServicesActive database + 0)); + THROW_LAST_ERROR_IF_NULL(scManager); + + wil::unique_schandle service( + OpenService(scManager.get(), ServiceCommon::kName, + SERVICE_USER_DEFINED_CONTROL)); + THROW_LAST_ERROR_IF_NULL(service); + + SERVICE_STATUS serviceStatus; + THROW_IF_WIN32_BOOL_FALSE( + ControlService(service.get(), ServiceCommon::kControlLaunchAdminUI, + &serviceStatus)); +} + } // namespace Service diff --git a/src/windhawk/app/service.h b/src/windhawk/app/service.h index fc860b7..ec5a9bb 100644 --- a/src/windhawk/app/service.h +++ b/src/windhawk/app/service.h @@ -12,4 +12,7 @@ bool Start(std::optional runUiSessionId = std::nullopt); void Stop(bool disableAutoStart); +void LaunchAdminCmd(); +void LaunchAdminUI(); + } // namespace Service diff --git a/src/windhawk/app/service_common.h b/src/windhawk/app/service_common.h index 528baff..d0eff90 100644 --- a/src/windhawk/app/service_common.h +++ b/src/windhawk/app/service_common.h @@ -18,6 +18,16 @@ static inline constexpr WCHAR kEmergencyStopEventName[] = static inline constexpr WCHAR kSafeModeStopEventName[] = L"Global\\WindhawkServiceSafeModeStopEvent"; +static inline constexpr WCHAR kLaunchAdminCmdEventName[] = + L"Global\\WindhawkLaunchAdminCmdEvent"; + +static inline constexpr DWORD kControlLaunchAdminCmd = 128; + +static inline constexpr WCHAR kLaunchAdminUIEventName[] = + L"Global\\WindhawkLaunchAdminUIEvent"; + +static inline constexpr DWORD kControlLaunchAdminUI = 129; + struct ServiceInfo { DWORD version; DWORD processId;