UAC(User Account Control) 是 Windows 平臺的用戶許可權控制。它可以讓程式使用管理員許可權執行某些操作。 靜態 UAC 提權 靜態 UAC 提權讓程式一直運行在管理員許可權下,只要在項目設置里把 "UAC Execution Level" 設置為 "requireAdmin ...
UAC(User Account Control) 是 Windows 平臺的用戶許可權控制。它可以讓程式使用管理員許可權執行某些操作。
靜態 UAC 提權
靜態 UAC 提權讓程式一直運行在管理員許可權下,只要在項目設置里把 "UAC Execution Level" 設置為 "requireAdministrator"。這樣生成的 exe 文件圖標會自動加上一個小盾牌的角標 Overlay。執行 exe 文件會自動彈出 UAC 對話框。
靜態 UAC 提權對程式員來說是一種偷懶的辦法,只需要修改一個配置就行。但對用戶來說非常麻煩,每次打開程式都需要確認 UAC 對話框。比如“小黑盒加速器”,每次打開它都會彈 UAC 對話框。更奇葩的是“小黑盒加速器” 可以設置開機自啟,每次開機都會彈一個 UAC 對話框要你確認。
動態 UAC 提權
動態 UAC 提權讓程式一直運行在普通用戶許可權下,並且只有需要管理員許可權操作時才會彈出 UAC 對話框。這種做法比靜態 UAC 提權更加細緻。一個普通的應用程式 99% 的功能都不需要管理員許可權,只在極少數情況下才需要。比如“QQ音樂”,它只是一個音樂播放軟體。用戶大部分的時間都僅使用音樂播放功能。而需要管理員許可權的“將QQ音樂設為預設應用”功能很少會被使用。所以動態 UAC 提權很有必要。
按照微軟官方文檔 Developing Applications that Require Administrator Privilege,有四種方法可以實現動態 UAC 提權:
- Elevated Task Model
- Operating System Service Model
- Administrator Broker Model
- Administrator COM Object Model
可以根據具體需要實現的功能選擇合適的方法。比如:“添加防火牆規則”這個功能需要使用 INetFwPolicy2。這是一個 COM 介面,可以直接用第 4 種方法“Administrator COM Object Model”實現。使用這種方法有個前提,就是這個 COM 介面必須在註冊表裡是配置為可以提權的。比如 INetFwPolicy2 介面,先找到 NetFwPolicy2 的 GUID 為 E2B3C97F-6AE1-41AC-817A-F6F92166D7DD
,再打開 regedit,輸入 HKLM\Software\Classes\CLSID\{E2B3C97F-6AE1-41AC-817A-F6F92166D7DD}\Elevation
。Enabled
值為 1
就可以用。對於這種方法,The COM Elevation Moniker 介紹得比較詳細,所以直接貼出完整代碼:
#include <Windows.h>
#include <netfw.h>
#include <comdef.h>
#include <spdlog/spdlog.h>
#include <memory>
#include <filesystem>
#include <wil/resource.h>
#include <wil/com.h>
#include <iostream>
int add_firewall_rule();
int main()
{
add_firewall_rule();
}
int add_firewall_rule()
{
auto couninitialize_call = wil::CoInitializeEx();
auto pNetFwPolicy2 = wil::CoCreateInstance<INetFwPolicy2>(__uuidof(NetFwPolicy2));
wil::unique_cotaskmem_string clsid;
RETURN_IF_FAILED(StringFromCLSID(__uuidof(NetFwPolicy2), &clsid));
// https://learn.microsoft.com/en-us/windows/win32/secauthz/developing-applications-that-require-administrator-privilege
// https://learn.microsoft.com/en-us/windows/win32/com/the-com-elevation-moniker
BIND_OPTS3 bo{};
bo.cbStruct = sizeof(bo);
bo.hwnd = GetConsoleWindow();
bo.dwClassContext = CLSCTX_LOCAL_SERVER;
auto moniker = fmt::format(L"Elevation:Administrator!new:{}", clsid.get());
spdlog::info(L"moniker: {}", moniker);
auto hr = (CoGetObject(moniker.c_str(), &bo, IID_PPV_ARGS(&pNetFwPolicy2)));
wil::com_ptr<INetFwRules> pNetFwRules;
RETURN_IF_FAILED(pNetFwPolicy2->get_Rules(&pNetFwRules));
long count{};
RETURN_IF_FAILED(pNetFwRules->get_Count(&count));
spdlog::info("rule count: {}", count);
wil::com_ptr<IUnknown> pEnumerator;
pNetFwRules->get__NewEnum(&pEnumerator);
auto pVariant = pEnumerator.query<IEnumVARIANT>();
std::vector<wchar_t> buf(1024);
GetModuleFileNameW(nullptr, buf.data(), (DWORD)buf.size());
std::filesystem::path exe_path(buf.data());
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ics/c-enumerating-firewall-rules
while (true) {
wil::unique_variant var;
ULONG cFecthed = 0;
if (pVariant->Next(1, &var, &cFecthed) != S_OK) {
break;
}
wil::com_ptr<INetFwRule> pNetFwRule;
var.pdispVal->QueryInterface(__uuidof(INetFwRule), (void**)&pNetFwRule);
_bstr_t app_name;
pNetFwRule->get_ApplicationName(app_name.GetAddress());
std::error_code ec{};
if (!app_name || !std::filesystem::equivalent(exe_path, (wchar_t*)app_name, ec)) {
continue;
}
pNetFwRule->put_Name(app_name);
pNetFwRules->Remove(app_name);
spdlog::info(L"remove firewall rule: {}", (wchar_t*)app_name);
}
auto pNetFwRule = wil::CoCreateInstance<INetFwRule3>(__uuidof(NetFwRule));
pNetFwRule->put_Enabled(VARIANT_TRUE);
pNetFwRule->put_Action(NET_FW_ACTION_ALLOW);
pNetFwRule->put_ApplicationName(_bstr_t(exe_path.c_str()));
pNetFwRule->put_Profiles(NET_FW_PROFILE2_ALL);
pNetFwRule->put_Name(_bstr_t(L"firewall-test.exe(TCP-In)"));
pNetFwRule->put_Protocol(NET_FW_IP_PROTOCOL_TCP);
RETURN_IF_FAILED(pNetFwRules->Add(pNetFwRule.get()));
pNetFwRule->put_Name(_bstr_t(L"firewall-test.exe(UDP-In)"));
pNetFwRule->put_Protocol(NET_FW_IP_PROTOCOL_UDP);
RETURN_IF_FAILED(pNetFwRules->Add(pNetFwRule.get()));
spdlog::info("success");
return S_OK;
}
關於動態 UAC 提權的代碼只有第 30-36 行。需要註意的點有:
- 第 32 行的
bo.hwnd
設置 UAC 對話框的 owner。如果代碼是控制台程式,應該設置為GetConsoleWindow()
,如果是 GUI 程式可以直接填nullptr
。 - 第 33 行的
bo.dwClassContext
應填CLSCTX_LOCAL_SERVER
,而不是CLSCTX_INPROC_SERVER
。否則提權失敗,CoGetObject
仍然返回S_OK
。 - UAC 對話框出現在
CoGetObject
調用時,而不是執行需要管理員許可權的操作(如pNetFwRules->Add
)時。 - 每次執行
add_firewall_rule()
都會彈出 UAC 對話框。
很多動態 UAC 提權的程式都會在需要管理員許可權的操作按鈕上顯示一個小盾牌圖標,表示點擊它會請求管理員許可權,比如:
可以直接調用 SHGetStockIconInfo
方法直接得到它的 HICON 句柄。
SHSTOCKICONINFO sii{};
sii.cbSize = sizeof(sii);
HRESULT hr = SHGetStockIconInfo(SIID_SHIELD, SHGSI_ICON | SHGSI_SMALLICON, &sii);
m_buttonRepairFirewall.SetIcon(sii.hIcon);
DestroyIcon(sii.hIcon);
本文來自博客園,作者:mkckr0,轉載請註明原文鏈接:https://www.cnblogs.com/mkckr0/p/17976467