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:
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); }