WIN32 動態 UAC 提權

来源:https://www.cnblogs.com/mkckr0/p/17976467
-Advertisement-
Play Games

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 對話框。

image-20240120115154732

靜態 UAC 提權對程式員來說是一種偷懶的辦法,只需要修改一個配置就行。但對用戶來說非常麻煩,每次打開程式都需要確認 UAC 對話框。比如“小黑盒加速器”,每次打開它都會彈 UAC 對話框。更奇葩的是“小黑盒加速器” 可以設置開機自啟,每次開機都會彈一個 UAC 對話框要你確認。

動態 UAC 提權

動態 UAC 提權讓程式一直運行在普通用戶許可權下,並且只有需要管理員許可權操作時才會彈出 UAC 對話框。這種做法比靜態 UAC 提權更加細緻。一個普通的應用程式 99% 的功能都不需要管理員許可權,只在極少數情況下才需要。比如“QQ音樂”,它只是一個音樂播放軟體。用戶大部分的時間都僅使用音樂播放功能。而需要管理員許可權的“將QQ音樂設為預設應用”功能很少會被使用。所以動態 UAC 提權很有必要。

按照微軟官方文檔 Developing Applications that Require Administrator Privilege,有四種方法可以實現動態 UAC 提權:

可以根據具體需要實現的功能選擇合適的方法。比如:“添加防火牆規則”這個功能需要使用 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}\ElevationEnabled 值為 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 提權的程式都會在需要管理員許可權的操作按鈕上顯示一個小盾牌圖標sheild,表示點擊它會請求管理員許可權,比如:

image-20240120141602183

可以直接調用 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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 在 上一小節 我介紹了我學習vue入門 插值語法 的過程。 在本篇,我將記錄我對vue的 計算屬性和偵聽器 的學習記錄 註:本篇對於”偵聽“和”監聽“是一個意思 一、計算屬性 在官網上,可以看到這樣一個例子: <div id="example"> {{ message.split('').re ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 移動端 H5 Tab 如何滾動居中 Tab 在 PC 端、移動端應用都上很常見,不過 Tab 在移動端 比 PC 端更複雜。為什麼呢?移動端設備屏幕較窄,一般僅能展示 4 ~ 7 個 Item。考慮到用戶體驗,UI 往往要求程式員實現一個 ...
  • selenium4框架學習 https://blog.csdn.net/qq_45158700/article/details/135363339 瀏覽器驅動&selenium文檔下載 Selenium with Python中文翻譯文檔:https://selenium-python-zh.rea ...
  • 1.約束(constraint)概述 1.1 為什麼需要約束 數據完整性(Data Integrity)是指數據的精確性(Accuracy)和可靠性(Reliability)。它是防止資料庫中存在不符合語義規定的數據和防止因錯誤信息的輸入輸出造成的無效操作或錯誤信息而提出的。 為了保證數據的完整性, ...
  • 個人博客:無奈何楊(wnhyang) 個人語雀:wnhyang 共用語雀:線上知識共用 Github:wnhyang - Overview 前文講了Sa-Token介紹與SpringBoot環境下使用,但是satoken最重要的登錄鑒權直接略過了,那這篇文章就開講,😂當然不是啦。看標題就知道這次要 ...
  • 二叉樹 前言 二叉樹的遍歷主要有深度優先遍歷和廣度優先遍歷,深度優先遍歷是優先訪問一個子樹上的所有節點,訪問的屬性是豎向的,而廣度優先遍歷則是優先訪問同一層的所有節點,訪問屬性是橫向的。 深度優先遍歷 深度優先遍歷主要有三種順序: 前序遍歷 —— 根左右 中序遍歷 —— 左根右 後序遍歷 —— 左右 ...
  • 在現代社會中,時間的規劃和安排對於個人和企業來說非常重要。在我們處理時間上的事務時,我們需要知道某一天是否是國家法定節假日或者法定工作日。因此,開發一個能夠查詢特定日期的法定工作日的API介面就變得非常有必要了。 一、功能說明 該API介面的主要功能是根據用戶輸入的日期,返回該日期是否是國家法定節假 ...
  • Rust 所有權和 Move 語義 所有權和生命周期是 Rust 和其它編程語言的主要區別,也是 Rust 其它知識點的基礎。 動態數組因為大小在編譯期無法確定,所以放在堆上,並且在棧上有一個包含了長度和容量的胖指針指向堆上的記憶體。 恰到好處的限制,反而會釋放無窮的創意和生產力。 Rust 所有權規 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...