Just Let It Flow

March 2, 2011

U-A-Ceen Nothing Yet

Filed under: Code,Windows — adeyblue @ 1:38 am

Detecting whether UAC is enabled or not is something I’ve never needed to do. I can’t really see how it can affect anything you architect one way or another but nevertheless, some people think it’s necessary and nice to know.

While investigating something else, I stumbled across a more accurate method of making the determination as opposed to reading the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA registry value. Looking around the Internet, it seems reading that key is often touted as the way to do it. There’s nothing wrong with using this method, well, apart for one caveat: if the state of UAC has changed and the computer hasn’t restarted yet, you’ll get an erroneous result [1].

Being Windows, and its tens of thousands of undocumented functions, you’d expect there to be at least one function that returns whether an integral part of the OS is enabled or not. A quick search of exports with ‘elevat’ in the name in the big ol’ database returned these matches for Vista:

Functions with "elevat" in their name in Vista SP0

Functions with "elevat" in their name in Vista SP0

Knowing that all the useful goodies are hidden in either kernel32 or ntdll, and having a name exactly along the lines of what we want to do, CheckElevationEnabled seemed like a great place to start.

                 public _CheckElevationEnabled@4
 
 var_4           = dword ptr -4
 pBool           = dword ptr  8
 
                 mov     edi, edi
                 push    ebp
                 mov     ebp, esp             ; standard prolog
                 push    ecx                  ; make some stack space
                 lea     eax, [ebp+var_4]     ; get a pointer to it
                 push    eax                  ; pass it on
                 call    ds:__imp__RtlQueryElevationFlags@4 ; RtlQueryElevationFlags(&x)
                 test    eax, eax
                 jl      short loc_77C200DF   ; jump if !NT_SUCCESS
                 mov     eax, [ebp+var_4]     ; put the filled in value in eax
                 mov     ecx, [ebp+pBool]     ; move our pointer to ecx
                 and     eax, 1               ; mask off all but the lowest bit
                 mov     [ecx], eax           ; here's how we know pBool is a pointer
                 xor     eax, eax             ; set error to STATUS_SUCCESS
 
 loc_77C200DF:                           ; CODE XREF: CheckElevationEnabled(x)+12
                 push    eax
                 call    ds:__imp__RtlNtStatusToDosErrorNoTeb@4 ; translate STATUS_* to ERROR_*
                 leave
                 retn    4

Or in C

DWORD WINAPI CheckElevationEnabled(BOOL* pResult)
{
    DWORD x;
    NTSTATUS stat = RtlQueryElevationFlags(&x);
    if(NT_SUCCESS(stat))
    {
        *pResult = (x & 1);
    }
    return RtlNtStatusToDosErrorNoTeb(stat);
}

Unfortunately, it didn’t really clear anything up. It’s obviously checking whether a flag/bit is set, but there’s no info on what the flag refers to or where it’s coming from. It’d be easy to guess it is doing what we’d like it to but more spelunking is needed, next stop RtlQueryElevationFlags:

_RtlQueryElevationFlags@4 proc near
 
 pFlags           = dword ptr  8
 
                 mov     edi, edi
                 push    ebp
                 mov     ebp, esp
                 mov     eax, [ebp+pFlags]
                 and     dword ptr [eax], 0        ; clear the slate
                 test    byte ptr ds:7FFE02F0h, 2  ; test the second bit of the address
                 jz      short loc_77CEEE74        ; go to next if not set
                 mov     dword ptr [eax], 1        ; set flags to one if it is
 
 loc_77CEEE74:                           ; CODE XREF: RtlQueryElevationFlags(x)+12
                 test    byte ptr ds:7FFE02F0h, 4  ; test the third bit
                 jz      short loc_77CEEE80        ; skip to next test if not set
                 or      dword ptr [eax], 2        ; or on the relevant flag is it is
 
 loc_77CEEE80:                           ; CODE XREF: RtlQueryElevationFlags(x)+21
                 test    byte ptr ds:7FFE02F0h, 8  ; test the fourth bit
                 jz      short loc_77CEEE8C        ; skip to exit
                 or      dword ptr [eax], 4        ; or on the relevant flag if it is
 
 loc_77CEEE8C:                           ; CODE XREF: RtlQueryElevationFlags(x)+2D
                 xor     eax, eax                  ; return STATUS_SUCCESS
                 pop     ebp
                 retn    4
 _RtlQueryElevationFlags@4 endp

Rather strangely, the data it queries comes directly from a byte at a hardcoded address offset rather than via a kernel API call or a specific global pointer. From a strictly disassembly point of view, there’s absolutely nothing new to go on to figure what the flags mean or represent. It did show that the error checking in CheckElevationEnabled is superfluous but other than that, nothing.

Of course, this is Windows and ntdll specifically, so it’s unlikely that this requires a look at the source to figure out. Taking off the obvious 2f0 offset, googling or querying the address in WinDGB [2] shows that 0x7ffe0000 is the address where the KUSER_SHARED_DATA [3] (also called UserSharedData) structure lives. This structure is just a big bunch of parameters shared between user and kernel mode in memory that is mapped to the same address in every process. Forget PHP, this is a true superglobal.

Knowing the name makes the format easy to figure out. One option is to use a tool like Dia2Dump to list all the types and members from ntdll’s symbols and grep the results. In a nice change of pace for this specific struct however, looking at the wdm.h or ntddk.h header files in the WDK, reveal it in C form, completely documented, and with some helpful comments to boot.

Back to the matter at hand, checking the struct for offset 0x2f0 reveals the queried structure bits to be:

union {
   ULONG SharedDataFlags;
   struct {
 
        //
        // The following bit fields are for the debugger only. Do not use.
        // Use the bit definitions instead.
        //
 
        ULONG DbgErrorPortPresent       : 1;
        ULONG DbgElevationEnabled       : 1; // second bit 
        ULONG DbgVirtEnabled            : 1; // third bit
        ULONG DbgInstallerDetectEnabled : 1; // fourth bit
        ULONG DbgSystemDllRelocated     : 1;
        ULONG DbgDynProcessorEnabled    : 1;
        ULONG DbgSEHValidationEnabled   : 1;
        ULONG SpareBits                 : 25;
    } DUMMYSTRUCTNAME2;
} DUMMYUNIONNAME2;

It’s nice to put a name to data. Names which coincide with the settings found in the registry key are especially pleasing as it means this is almost undoubtedly the in-memory equivalent of those values. In order, the second bit denotes whether UAC is enabled, the third failed registry/file write virtualisation, and the fourth whether installers are detected and automatically elevated.

From the slight chance of wrong data from the registry, the train arrives into its station with an extra three methods of querying UAC. A direct read of a hardcoded address (not that that’s recommend), a straight call to RtlQueryElevationFlags [4], or if you only care about UAC and not virtualisation or installer detection, CheckElevationEnabled.


Notes

[1] Despite the stated limitation, this is the method employed by ?IEStubIsLuaEnabled@@YGHXZ, ordinal 30 in iertutil.dll on Vista and above

[2] WinDBG has the !ksuser extension to display the data contained in the KUSER_SHARED_DATA mapping

[3] KUSER_SHARED_DATA contents from Windows 7 SP1 32-bit layout via ntdll.dll’s symbols.
To dump it yourself, get the PDB and run ‘dia2dump -t “A:\Path\To\ntdll\symbols.pdb” > symboltypes.txt’
See ntddk.h or wdm.h in the DDK for the struct in C format

UserDefinedType: _KUSER_SHARED_DATA
Data           :   this+0x0, Member, Type: unsigned long, TickCountLowDeprecated
Data           :   this+0x4, Member, Type: unsigned long, TickCountMultiplier
Data           :   this+0x8, Member, Type: volatile struct _KSYSTEM_TIME, InterruptTime
UserDefinedType:     _KSYSTEM_TIME
 
Data           :   this+0x14, Member, Type: volatile struct _KSYSTEM_TIME, SystemTime
UserDefinedType:     _KSYSTEM_TIME
 
Data           :   this+0x20, Member, Type: volatile struct _KSYSTEM_TIME, TimeZoneBias
UserDefinedType:     _KSYSTEM_TIME
 
Data           :   this+0x2C, Member, Type: unsigned short, ImageNumberLow
Data           :   this+0x2E, Member, Type: unsigned short, ImageNumberHigh
Data           :   this+0x30, Member, Type: wchar_t[0x104], NtSystemRoot
Data           :   this+0x238, Member, Type: unsigned long, MaxStackTraceDepth
Data           :   this+0x23C, Member, Type: unsigned long, CryptoExponent
Data           :   this+0x240, Member, Type: unsigned long, TimeZoneId
Data           :   this+0x244, Member, Type: unsigned long, LargePageMinimum
Data           :   this+0x248, Member, Type: unsigned long[0x7], Reserved2
Data           :   this+0x264, Member, Type: enum _NT_PRODUCT_TYPE, NtProductType
Data           :   this+0x268, Member, Type: unsigned char, ProductTypeIsValid
Data           :   this+0x26C, Member, Type: unsigned long, NtMajorVersion
Data           :   this+0x270, Member, Type: unsigned long, NtMinorVersion
Data           :   this+0x274, Member, Type: unsigned char[0x40], ProcessorFeatures
Data           :   this+0x2B4, Member, Type: unsigned long, Reserved1
Data           :   this+0x2B8, Member, Type: unsigned long, Reserved3
Data           :   this+0x2BC, Member, Type: volatile unsigned long, TimeSlip
Data           :   this+0x2C0, Member, Type: enum _ALTERNATIVE_ARCHITECTURE_TYPE, AlternativeArchitecture
Data           :   this+0x2C4, Member, Type: unsigned long[0x1], AltArchitecturePad
Data           :   this+0x2C8, Member, Type: union _LARGE_INTEGER, SystemExpirationDate
UserDefinedType:     _LARGE_INTEGER
 
Data           :   this+0x2D0, Member, Type: unsigned long, SuiteMask
Data           :   this+0x2D4, Member, Type: unsigned char, KdDebuggerEnabled
Data           :   this+0x2D5, Member, Type: unsigned char, NXSupportPolicy
Data           :   this+0x2D8, Member, Type: volatile unsigned long, ActiveConsoleId
Data           :   this+0x2DC, Member, Type: volatile unsigned long, DismountCount
Data           :   this+0x2E0, Member, Type: unsigned long, ComPlusPackage
Data           :   this+0x2E4, Member, Type: unsigned long, LastSystemRITEventTickCount
Data           :   this+0x2E8, Member, Type: unsigned long, NumberOfPhysicalPages
Data           :   this+0x2EC, Member, Type: unsigned char, SafeBootMode
Data           :   this+0x2ED, Member, Type: unsigned char, TscQpcData
Data           :   this(bf)+0x2ED:0x0 len(0x1), Member, Type: unsigned char, TscQpcEnabled
Data           :   this(bf)+0x2ED:0x1 len(0x1), Member, Type: unsigned char, TscQpcSpareFlag
Data           :   this(bf)+0x2ED:0x2 len(0x6), Member, Type: unsigned char, TscQpcShift
Data           :   this+0x2EE, Member, Type: unsigned char[0x2], TscQpcPad
Data           :   this+0x2F0, Member, Type: unsigned long, SharedDataFlags
Data           :   this(bf)+0x2F0:0x0 len(0x1), Member, Type: unsigned long, DbgErrorPortPresent
Data           :   this(bf)+0x2F0:0x1 len(0x1), Member, Type: unsigned long, DbgElevationEnabled
Data           :   this(bf)+0x2F0:0x2 len(0x1), Member, Type: unsigned long, DbgVirtEnabled
Data           :   this(bf)+0x2F0:0x3 len(0x1), Member, Type: unsigned long, DbgInstallerDetectEnabled
Data           :   this(bf)+0x2F0:0x4 len(0x1), Member, Type: unsigned long, DbgSystemDllRelocated
Data           :   this(bf)+0x2F0:0x5 len(0x1), Member, Type: unsigned long, DbgDynProcessorEnabled
Data           :   this(bf)+0x2F0:0x6 len(0x1), Member, Type: unsigned long, DbgSEHValidationEnabled
Data           :   this(bf)+0x2F0:0x7 len(0x19), Member, Type: unsigned long, SpareBits
Data           :   this+0x2F4, Member, Type: unsigned long[0x1], DataFlagsPad
Data           :   this+0x2F8, Member, Type: unsigned __int64, TestRetInstruction
Data           :   this+0x300, Member, Type: unsigned long, SystemCall
Data           :   this+0x304, Member, Type: unsigned long, SystemCallReturn
Data           :   this+0x308, Member, Type: unsigned __int64[0x3], SystemCallPad
Data           :   this+0x320, Member, Type: volatile struct _KSYSTEM_TIME, TickCount
UserDefinedType:     _KSYSTEM_TIME
 
Data           :   this+0x320, Member, Type: volatile unsigned __int64, TickCountQuad
Data           :   this+0x320, Member, Type: unsigned long[0x3], ReservedTickCountOverlay
Data           :   this+0x32C, Member, Type: unsigned long[0x1], TickCountPad
Data           :   this+0x330, Member, Type: unsigned long, Cookie
Data           :   this+0x334, Member, Type: unsigned long[0x1], CookiePad
Data           :   this+0x338, Member, Type: __int64, ConsoleSessionForegroundProcessId
Data           :   this+0x340, Member, Type: unsigned long[0x10], Wow64SharedInformation
Data           :   this+0x380, Member, Type: unsigned short[0x10], UserModeGlobalLogger
Data           :   this+0x3A0, Member, Type: unsigned long, ImageFileExecutionOptions
Data           :   this+0x3A4, Member, Type: unsigned long, LangGenerationCount
Data           :   this+0x3A8, Member, Type: unsigned __int64, Reserved5
Data           :   this+0x3B0, Member, Type: volatile unsigned __int64, InterruptTimeBias
Data           :   this+0x3B8, Member, Type: volatile unsigned __int64, TscQpcBias
Data           :   this+0x3C0, Member, Type: volatile unsigned long, ActiveProcessorCount
Data           :   this+0x3C4, Member, Type: volatile unsigned short, ActiveGroupCount
Data           :   this+0x3C6, Member, Type: unsigned short, Reserved4
Data           :   this+0x3C8, Member, Type: volatile unsigned long, AitSamplingValue
Data           :   this+0x3CC, Member, Type: volatile unsigned long, AppCompatFlag
Data           :   this+0x3D0, Member, Type: unsigned __int64, SystemDllNativeRelocation
Data           :   this+0x3D8, Member, Type: unsigned long, SystemDllWowRelocation
Data           :   this+0x3DC, Member, Type: unsigned long[0x1], XStatePad
Data           :   this+0x3E0, Member, Type: struct _XSTATE_CONFIGURATION, XState

[4] Here’s a simple wrapper for RtlQueryElevationFlags so you don’t have to bother with the function typedefs and all that jazz.

enum ElevationFlags
{
    ELEVATION_UAC_ENABLED = 1,
    ELEVATION_VIRTUALIZATION_ENABLED = 2,
    ELEVATION_INSTALLER_DETECTION_ENABLED = 4
};
 
void GetElevationFlags(ElevationFlags* pFlags)
{
    assert(pFlags);
    typedef NTSTATUS (NTAPI*pfnRtlQueryElevationFlags)(ElevationFlags*);
    HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
    pfnRtlQueryElevationFlags rtlQueryElevationFlags = (pfnRtlQueryElevationFlags)GetProcAddress(hNtdll, "RtlQueryElevationFlags");
    assert(rtlQueryElevationFlags);
    rtlQueryElevationFlags(pFlags);
}

Powered by WordPress