WIN32 动态 UAC 提权
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);
總結
以上是生活随笔為你收集整理的WIN32 动态 UAC 提权的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【scikit-learn基础】--『监
- 下一篇: 深度学习-神经网络原理-39