UAC, Elevation and the default Administrator account

28 08 2012

These days I was struggling with Windows 7 UAC (User Account Control). We have an (.NET) app where we want to detect if we are running as an elevated process (if UAC is turned on), or if we are a member of the Administrators group (fallback solution when UAC is turned off). There is a lot of good information including C# sample code around (see e. g. here). They recommend to check the registry for the EnableLUA flag to determine if UAC is turned on, and then call OpenProcessToken and GetTokenInformation(…, TokenElevationType, …) to retrieve the elevation type of the current process.

The problem with all these solutions is that they assume that – when UAC is turned on – the elevation will be either TokenElevationTypeLimited (when not running elevated) or TokenElevationTypeFull (when running elevated). But this is not true! There are times when you receive TokenElevationTypeDefault even though you might expect the value to be TokenElevationTypeFull.

You will receive TokenElevationTypeDefault even when UAC is turned on when the following preconditions are met:

  1. The default Administrator account is enabled (default: disabled), and
  2. The security policy „User Account Control: Use Admin Approval Mode for the built-in Administrator account“ is disabled. This is the default.

You can use the Local Security Policy Editor (secpol.msc) and navigate to Security Settings – Local Policies – Security Options to verify this option.

When the above prerequisites are met, you will receive TokenElevationTypeDefault when one of the following conditions is true:

  1. You are running as the default Administrator, or
  2. You started the process through the „Run as administrator“ Explorer context menu and selected the default Administrator account.

In other words: When the security option „User Account Control: Use Admin Approval Mode for the built-in Administrator account“ is disabled, the default Administrator account behaves as if UAC was also disabled.

To make our app behave the same no matter which Administrator account is used (the default one, or any of the other existing accounts which are a member of the Administrators group), I enhanced the solution for determining process elevation as follows:

/// <summary>
/// Base on code found here:
/// http://stackoverflow.com/questions/1220213/c-detect-if-running-with-elevated-privileges
/// </summary>
public static class UacHelper
{
  private const string uacRegistryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System";
  private const string uacRegistryValue = "EnableLUA";

  private const uint STANDARD_RIGHTS_READ = 0x00020000;
  private const uint TOKEN_QUERY = 0x0008;
  private const uint TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY);

  [DllImport("advapi32.dll", SetLastError = true)]
  [return: MarshalAs(UnmanagedType.Bool)]
  static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle);

  [DllImport("advapi32.dll", SetLastError = true)]
  public static extern bool GetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, 
                                                IntPtr TokenInformation, uint TokenInformationLength, 
                                                out uint ReturnLength);

  public enum TOKEN_INFORMATION_CLASS
  {
    TokenUser = 1,
    TokenGroups,
    TokenPrivileges,
    TokenOwner,
    TokenPrimaryGroup,
    TokenDefaultDacl,
    TokenSource,
    TokenType,
    TokenImpersonationLevel,
    TokenStatistics,
    TokenRestrictedSids,
    TokenSessionId,
    TokenGroupsAndPrivileges,
    TokenSessionReference,
    TokenSandBoxInert,
    TokenAuditPolicy,
    TokenOrigin,
    TokenElevationType,
    TokenLinkedToken,
    TokenElevation,
    TokenHasRestrictions,
    TokenAccessInformation,
    TokenVirtualizationAllowed,
    TokenVirtualizationEnabled,
    TokenIntegrityLevel,
    TokenUIAccess,
    TokenMandatoryPolicy,
    TokenLogonSid,
    MaxTokenInfoClass
  }

  public enum TOKEN_ELEVATION_TYPE
  {
    TokenElevationTypeDefault = 1,
    TokenElevationTypeFull,
    TokenElevationTypeLimited
  }

  private static bool? _isUacEnabled;
  public static bool IsUacEnabled
  {
    get
    {
    if (_isUacEnabled == null)
    {
      var uacKey = Registry.LocalMachine.OpenSubKey(uacRegistryKey, false);
      if (uacKey == null)
      {
      _isUacEnabled = false;
      }
      else
      {
      var enableLua = uacKey.GetValue(uacRegistryValue);
      _isUacEnabled = enableLua.Equals(1);
      }
    }
    return _isUacEnabled.Value;
    }
  }

  private static bool? _isAdministrator;
  public static bool IsAdministrator
  {
    get
    {
    if (_isAdministrator == null)
    {
      var identity = WindowsIdentity.GetCurrent();
      Debug.Assert(identity != null);
      var principal = new WindowsPrincipal(identity);
      _isAdministrator = principal.IsInRole(WindowsBuiltInRole.Administrator);
    }
    return _isAdministrator.Value;
    }
  }

  private static bool? _isProcessElevated; 
  public static bool IsProcessElevated
  {
    get
    {
    if (_isProcessElevated == null)
    {
      if (IsUacEnabled)
      {
      var process = Process.GetCurrentProcess();

      IntPtr tokenHandle;
      if (!OpenProcessToken(process.Handle, TOKEN_READ, out tokenHandle))
      {
        throw new ApplicationException("Could not get process token.  Win32 Error Code: " + Marshal.GetLastWin32Error());
      }

      var elevationResult = TOKEN_ELEVATION_TYPE.TokenElevationTypeDefault;

      var elevationResultSize = Marshal.SizeOf((int)elevationResult);
      uint returnedSize;
      var elevationTypePtr = Marshal.AllocHGlobal(elevationResultSize);

      var success = GetTokenInformation(tokenHandle, TOKEN_INFORMATION_CLASS.TokenElevationType, elevationTypePtr, (uint)elevationResultSize, out returnedSize);
      if (!success)
      {
        Marshal.FreeHGlobal(elevationTypePtr);
        throw new ApplicationException("Unable to determine the current elevation.");
      }

      elevationResult = (TOKEN_ELEVATION_TYPE)Marshal.ReadInt32(elevationTypePtr);
      Marshal.FreeHGlobal(elevationTypePtr);

      // Special test for TokenElevationTypeDefault.
      // If the current user is the default Administrator, then the
      // process is also assumed to run elevated. This is assumed 
      // because by default the default Administrator (which is disabled by default) 
      //  gets all access rights even without showing a UAC prompt.
      switch (elevationResult)
      {
        case TOKEN_ELEVATION_TYPE.TokenElevationTypeFull:
        _isProcessElevated = true;
        break;
        case TOKEN_ELEVATION_TYPE.TokenElevationTypeLimited:
        _isProcessElevated = false;
        break;
        default:
        // Will come here if either
        // 1. We are running as the default Administrator.
        // 2. We were started using "Run as administrator" from a non-admin
        //    account and logged on as the default Administrator account from
        //    the list of available Administrator accounts.
        //
        // Note: By default the default Administrator account always behaves 
        //       as if UAC was turned off. 
        //
        // This can be controlled through the Local Security Policy editor 
        // (secpol.msc) using the 
        // "User Account Control: Use Admin Approval Mode for the built-in Administrator account"
        // option of the Security Settings\Local Policies\Security Options branch.
        _isProcessElevated = IsAdministrator;
        break;
      }
      }
      else
      {
      _isProcessElevated = IsAdministrator;
      }
      }
      return _isProcessElevated.Value;
    }
  }
}

Aktionen

Information

6 responses

31 03 2015
Tlhogi

Hi Chris

than ks for the nice article, how can you modify your sample to check elevation for another process,this gives an error var process = Process.GetProcessByname(„process.exe“);

31 03 2015
candritzky

@Tlhogi: There is no such method Process.GetProcessByname. Try GetProcessesByName (pluralized!) and try to leave out the „.exe“ extension (see also samples for GetProcessesByName in the docs).

Then change the static IsProcessElevated property to a (static) method that takes a Process instance as an input argument. Replace Process.GetCurrentProcess with this argument.

31 03 2015
Tlhogi

Thanks for your reply, and what would this line be?
IntPtr tokenHandle;
if (!OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_READ, out tokenHandle))

31 03 2015
Tlhogi

Hi Chris what i mean is OpenprocessToken() first argument is a handle but i can’t seem to find the process handle from using GetProcessesByName?

31 03 2015
candritzky

@Tlhogi: I reworked the code a little bit. It is now

var process = Process.GetCurrentProcess();
OpenProcessToken(process.Handle, …);

The Process class has a Handle property.

31 03 2015
Tlhogi

What i really want though is to get the process by its name and this is giving access denied for some processes: var process = Process.GetProcessById(Process.GetProcessesByName(_Process)[0].Id);

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s




%d Bloggern gefällt das: