U-A-Ceen Nothing Yet

Published April 29, 2011
Advertisement
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 [sup][1][/sup].

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:

elevated.png

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

[source lang="asm"]
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
[/source]
Or in C
[source lang="c"]
DWORD WINAPI CheckElevationEnabled(BOOL* pResult)
{
DWORD x;
NTSTATUS stat = RtlQueryElevationFlags(&x);
if(NT_SUCCESS(stat))
{
*pResult = (x & 1);
}
return RtlNtStatusToDosErrorNoTeb(stat);
}
[/source]

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:

[source lang="asm"]
_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
[/source]

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 [sup][2][/sup] shows that 0x7ffe0000 is the address where the KUSER_SHARED_DATA [sup][3][/sup] (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:
[source lang="c"]
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;
[/source]

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 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 [sup][4][/sup], or if you only care about UAC and not virtualisation or installer detection, CheckElevationEnabled.

If only this was useful in some way, oh well.




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

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

[sup][3][/sup] 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.
[source lang="text"]
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
[/source]

[sup][4][/sup] Here's a simple wrapper for RtlQueryElevationFlags so you don't have to bother with the function typedefs and all that jazz.
[source lang="cpp"]
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);
}
[/source]
1 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement