1. 程式人生 > >C# WinForm判斷程式是否以管理員身份執行,UAC許可權的提權與降權

C# WinForm判斷程式是否以管理員身份執行,UAC許可權的提權與降權

另外新增2個相關文章只有提權,沒有提到降低許可權):


歡迎轉載,但最好請註明  Jero 翻譯。

  • 已提權、已經提升許可權的程序——可以理解為使用管理員許可權執行的。
  • 未提權、沒有提升許可權的程序——可以理解為使用 非 管理員許可權(既普通使用者許可權)執行的。

UAC機制是由Vista引出,並且由於Windows 7的核心與Vista框架相同,所以本文中提到的Vista的UAC許可權相關對於Windows 7完全適用。

正文中斜體都是我新增的說明。

正文

  當你為Windows Vista開發程式的時候,你常常遇到一個問題:如何使用程式設計技巧來控制一個應用程式的執行許可權(是否從普通使用者提權到管理員,或是從管理員降級到普通使用者)

。當用戶執行一個程式的時候,它的執行許可權是由應用程式清單(manifest)中的requestedExecutionLevel屬性值所決定的,以及Vista/7的使用者帳戶控制(UAC)根絕需要來採取適當的行為(例如彈出UAC提權確認的視窗,等)。然而,如果一個程式需要執行一個新的不同許可權的程式應該如何?

例如:

  • 一個程式普通執行的時候是採用非管理員的許可權(沒有經過提升許可權,普通使用者的許可權),然後在執行過程中檢測到了程式有新版本可用。為了能夠讓自身更新,它需要啟動一個單獨的程序來提升自身的許可權,這樣才能正確執行升級。在這種情況下,一個普通使用者的許可權需要建立一個新的提升到至少是管理員許可權的程式。
  • 大多數安裝程式讓使用者選擇在安裝結束以後執行其程式。安裝程式是以提權過後的程序執行的,但是新的程式需要以普通的,未經過提升的許可權來執行。

譯者注:簡單理解就是以普通使用者的許可權運行了A程式,然後用A程式執行B程式,但是B程式被提升到管理員許可權,而不是繼承A程式的許可權(如果開啟UAC的話,這個提權過程系統會彈出視窗進行確認的)

或是:C程式是通過管理員許可權執行的,然後用C程式執行D程式,但是D程式不繼承C程式的管理員許可權,而是降級到一個普通使用者的許可權。(對於降級,系統是不會提示的)

  微軟已經提供了一個相對簡單的方法來完成上面提到的第一個任務(即A程式使B程式提升許可權),通過指定ShellExecuteEx API的一個引數為“runas”。但是,由於某些原因,微軟並沒有提供一個相似的方法來執行一個相反的過程:從一個已提權的程式來啟動一個未提權的程式(即C程式使D程式降級)

。這篇文章中,我將會展示如何解決這個問題,以及相關的問題。


檢測當前程式的執行許可權

首先,如何檢測一個程式當前的執行許可權?原始碼中的 VistaTools.cxx 檔案包含的兩個函式對這個問題得到了答案。

第一個函式是 GetElevationType(),它使用了 Win32 API GetTokenInformation() 來獲得當前程式其令牌(token)的許可權型別,它所可能返回的值為:

    • TokenElevationTypeDefault - 使用者沒有使用一個分隔的令牌(許可權機制)。這個值說明了UAC已經禁用,或者是程式是有一個非管理員組的普通使用者執行的。
    • TokenElevationTypeFull - 程式已經獲得提權(至少是管理員許可權的)
    • TokenElevationTypeLimited - 程式沒有經過提權(普通許可權執行的)
注意:只有當UAC開啟的時候,後面2個值才能返回,而且當前使用者是管理員組中的一員(就是這個使用者有一個分隔的令牌<既是當前使用者是管理員,可以執行普通使用者許可權的程式,也可以執行管理員許可權的程式>
第二個函式是 IsElevated() 它也呼叫了 GetTokenInformation() API,但是它獲取的是TokenElevation 類的資訊。它只能返回下面兩項之一:
    • S_OK - 當前程序已經提權過。這個數值表明了UAC已經開啟,而且當前程式是由管理員提權的;或者UAC已經禁用了,但是當前使用者是管理員組中的一員。
    • S_FALSE - 當前程序沒有經過提權(受限的)。這個數值表明了UAC已經開啟了,而且當前程序只是普通的執行,沒有經過提權;或是UAC已經禁用了,程序只是由一個普通使用者執行。

使用這兩個函式,一個程式可以確定它執行的(許可權的)確切情況。

執行一個提權的程式

  如果一個未提權的程式需要執行一個提權的程式,所有它需要做的是呼叫 ShellExecuteEx() API ,然後指示一個引數為"runas",原始碼中的函式 RunElevated() 就是這樣達到提權目的的:
BOOL
RunElevated(    HWND hwnd,
        LPCTSTR pszPath,
        LPCTSTR pszParameters = NULL,
        LPCTSTR pszDirectory = NULL )
{
    SHELLEXECUTEINFO shex;

    memset( &shex, 0, sizeof( shex) );

    shex.cbSize        = sizeof( SHELLEXECUTEINFO );
    shex.fMask        = 0;
    shex.hwnd        = hwnd;
    shex.lpVerb        = _T("runas");
    shex.lpFile        = pszPath;
    shex.lpParameters    = pszParameters;
    shex.lpDirectory    = pszDirectory;
    shex.nShow        = SW_NORMAL;

    return ::ShellExecuteEx( &shex );
}

從一個已提權的程序來執行一個沒有提權的程序

  從相反的方向(從一個已經提權的程序來執行一個沒有提權的程序)變得非常複雜。如果父程序已經提權了的,那麼它所執行的所有子程式都將繼承它的已經提權的許可權,而且無論子程式的清單(manifest)中的 requestedExecutionLevel 屬性值是如何指定的。由於某些原因,微軟並沒有提供一個API來直接降低程序的許可權,所以我們需要想出一個間接的辦法來達到目的。
  這個訣竅是使用Windows Vista自帶的任務計劃程式(Task Scheduler)來建立以低許可權執行的任務,並且要求這個任務建立以後應當立即執行。最終的結果是相同的,猶如程序直接啟動一樣。

  本文的原始碼包含的函式 RunAsStdUser() 正是這麼做的。它是基於MSDN樣本“Registration Trigger Example(註冊任務以後立即執行的例子)”,而且它涉及了十幾個COM介面與任務計劃程式交流,以及設立一個任務通過普通(未提權)的許可權。我沒有將這些原始碼的函式包含在這裡,因為那是相當乏味的;你可以在 VistaTools.cxx 檔案中找到它。


通過事實來證明以上

  這個演示程式(VistaElevator)說明了如何通過程式設計方法來執行提權和降權。當你執行它的時候,它顯示了一個對話方塊來展示程序執行許可權的相關資訊,通過 GetElevationType() 和 IsElevated() 這兩個函式來獲取(函式說明看上面)。它也提供了你兩個如何重啟程序的選擇,提權或是取消。基於你的選擇,VistaElevator 呼叫 RunElevated() 或是 RunAsStdUser() 函式 (依舊看上面) 來重啟自身,並在要求的許可權下。

作者Andrei Belogortseff