3

Reversed this function. It works. But stepping through I can't figure out how. Why does this work?

bool   _Is64BitOS(void) {
    unsigned int version = *(unsigned int*)0x7FFE026C;
    unsigned int address = version == 10 ? 0x7FFE0308 : 0x7FFE0300;
    ILog("Running %u-bit system\n", *(void**)address ? 32 : 64);

    return (*(void**)address ? false : true);
};

Why do we find 0x0A at 0x7FFE026C on a 64 bit Windows install?

1
  • 1
    Just to mention the obvious, there isn't much pointer arithmethic involved. It's just using hardcoded addresses -- in one case conditionally one address or another.
    – 0xC0000022L
    Commented Jan 10, 2023 at 13:33

1 Answer 1

1

"Simple", I want to respond, but it's somewhat involved.

That region -- (since newer Windows 10 versions read-only) mapped into userspace of every program -- is known as KUSER_SHARED_DATA. You can find details about the offsets and there meaning over here (beware, it's a bit dated!).

The first assignment to version merely reads KUSER_SHARED_DATA::NtMajorVersion. So to answer:

Why do we find 0x0A at 0x7FFE026C on a 64 bit Windows install?

... it's the major version of the Windows, 10 in decimal representation.

Since we need an up-to-date view of what KUSER_SHARED_DATA looks we'll have a look at the official symbols from Windows 10 by starting WinDbg/WinDbgX on Windows 10, launching some 64-bit program from it (e.g. notepad.exe) and then running dt nt!_KUSER_SHARED_DATA to see the type definition.

0:000> dt nt!_KUSER_SHARED_DATA
ntdll!_KUSER_SHARED_DATA
   +0x000 TickCountLowDeprecated : Uint4B
   +0x004 TickCountMultiplier : Uint4B
   +0x008 InterruptTime    : _KSYSTEM_TIME
   +0x014 SystemTime       : _KSYSTEM_TIME
   +0x020 TimeZoneBias     : _KSYSTEM_TIME
   +0x02c ImageNumberLow   : Uint2B
   +0x02e ImageNumberHigh  : Uint2B
   +0x030 NtSystemRoot     : [260] Wchar
   +0x238 MaxStackTraceDepth : Uint4B
   +0x23c CryptoExponent   : Uint4B
   +0x240 TimeZoneId       : Uint4B
   +0x244 LargePageMinimum : Uint4B
   +0x248 AitSamplingValue : Uint4B
   +0x24c AppCompatFlag    : Uint4B
   +0x250 RNGSeedVersion   : Uint8B
   +0x258 GlobalValidationRunlevel : Uint4B
   +0x25c TimeZoneBiasStamp : Int4B
   +0x260 NtBuildNumber    : Uint4B
   +0x264 NtProductType    : _NT_PRODUCT_TYPE
   +0x268 ProductTypeIsValid : UChar
   +0x269 Reserved0        : [1] UChar
   +0x26a NativeProcessorArchitecture : Uint2B
   +0x26c NtMajorVersion   : Uint4B
   +0x270 NtMinorVersion   : Uint4B
   +0x274 ProcessorFeatures : [64] UChar
   +0x2b4 Reserved1        : Uint4B
   +0x2b8 Reserved3        : Uint4B
   +0x2bc TimeSlip         : Uint4B
   +0x2c0 AlternativeArchitecture : _ALTERNATIVE_ARCHITECTURE_TYPE
   +0x2c4 BootId           : Uint4B
   +0x2c8 SystemExpirationDate : _LARGE_INTEGER
   +0x2d0 SuiteMask        : Uint4B
   +0x2d4 KdDebuggerEnabled : UChar
   +0x2d5 MitigationPolicies : UChar
   +0x2d5 NXSupportPolicy  : Pos 0, 2 Bits
   +0x2d5 SEHValidationPolicy : Pos 2, 2 Bits
   +0x2d5 CurDirDevicesSkippedForDlls : Pos 4, 2 Bits
   +0x2d5 Reserved         : Pos 6, 2 Bits
   +0x2d6 CyclesPerYield   : Uint2B
   +0x2d8 ActiveConsoleId  : Uint4B
   +0x2dc DismountCount    : Uint4B
   +0x2e0 ComPlusPackage   : Uint4B
   +0x2e4 LastSystemRITEventTickCount : Uint4B
   +0x2e8 NumberOfPhysicalPages : Uint4B
   +0x2ec SafeBootMode     : UChar
   +0x2ed VirtualizationFlags : UChar
   +0x2ee Reserved12       : [2] UChar
   +0x2f0 SharedDataFlags  : Uint4B
   +0x2f0 DbgErrorPortPresent : Pos 0, 1 Bit
   +0x2f0 DbgElevationEnabled : Pos 1, 1 Bit
   +0x2f0 DbgVirtEnabled   : Pos 2, 1 Bit
   +0x2f0 DbgInstallerDetectEnabled : Pos 3, 1 Bit
   +0x2f0 DbgLkgEnabled    : Pos 4, 1 Bit
   +0x2f0 DbgDynProcessorEnabled : Pos 5, 1 Bit
   +0x2f0 DbgConsoleBrokerEnabled : Pos 6, 1 Bit
   +0x2f0 DbgSecureBootEnabled : Pos 7, 1 Bit
   +0x2f0 DbgMultiSessionSku : Pos 8, 1 Bit
   +0x2f0 DbgMultiUsersInSessionSku : Pos 9, 1 Bit
   +0x2f0 DbgStateSeparationEnabled : Pos 10, 1 Bit
   +0x2f0 SpareBits        : Pos 11, 21 Bits
   +0x2f4 DataFlagsPad     : [1] Uint4B
   +0x2f8 TestRetInstruction : Uint8B
   +0x300 QpcFrequency     : Int8B
   +0x308 SystemCall       : Uint4B
   +0x30c Reserved2        : Uint4B
   +0x310 SystemCallPad    : [2] Uint8B
   +0x320 TickCount        : _KSYSTEM_TIME
   +0x320 TickCountQuad    : Uint8B
   +0x320 ReservedTickCountOverlay : [3] Uint4B
   +0x32c TickCountPad     : [1] Uint4B
   +0x330 Cookie           : Uint4B
   +0x334 CookiePad        : [1] Uint4B
   +0x338 ConsoleSessionForegroundProcessId : Int8B
   +0x340 TimeUpdateLock   : Uint8B
   +0x348 BaselineSystemTimeQpc : Uint8B
   +0x350 BaselineInterruptTimeQpc : Uint8B
   +0x358 QpcSystemTimeIncrement : Uint8B
   +0x360 QpcInterruptTimeIncrement : Uint8B
   +0x368 QpcSystemTimeIncrementShift : UChar
   +0x369 QpcInterruptTimeIncrementShift : UChar
   +0x36a UnparkedProcessorCount : Uint2B
   +0x36c EnclaveFeatureMask : [4] Uint4B
   +0x37c TelemetryCoverageRound : Uint4B
   +0x380 UserModeGlobalLogger : [16] Uint2B
   +0x3a0 ImageFileExecutionOptions : Uint4B
   +0x3a4 LangGenerationCount : Uint4B
   +0x3a8 Reserved4        : Uint8B
   +0x3b0 InterruptTimeBias : Uint8B
   +0x3b8 QpcBias          : Uint8B
   +0x3c0 ActiveProcessorCount : Uint4B
   +0x3c4 ActiveGroupCount : UChar
   +0x3c5 Reserved9        : UChar
   +0x3c6 QpcData          : Uint2B
   +0x3c6 QpcBypassEnabled : UChar
   +0x3c7 QpcShift         : UChar
   +0x3c8 TimeZoneBiasEffectiveStart : _LARGE_INTEGER
   +0x3d0 TimeZoneBiasEffectiveEnd : _LARGE_INTEGER
   +0x3d8 XState           : _XSTATE_CONFIGURATION
   +0x710 FeatureConfigurationChangeStamp : _KSYSTEM_TIME
   +0x71c Spare            : Uint4B

(if we used dt -r2 nt!_KUSER_SHARED_DATA 0x000000007ffe0000 -- assuming we're not on 64-bit ARM where the KUSER_SHARED_DATA is no longer at a fixed address -- we can see the "decoded" contents of this structure)

By comparing this output with the one from Terminus (link above) we can see that we seem to be looking for KUSER_SHARED_DATA::SystemCall (in newer versions SystemCallPad as per Terminus, but the symbols call it SystemCall nevertheless). When we follow the logic from the function it appears as if it tries to dereference the given address, picked based on Windows 10 or not, as a pointer. The goal seems to be to figure out (from any given process) if that value is 0 or not. And that seems to be an indicator for whether or not we're on 32-bit.

KUSER_SHARED_DATA has all sorts of useful applications, one is to quickly determine the true Windows version with resorting to NT native function calls or having to specify in a manifest that your application is compatible with the Windows version it's running on.

That said, it's always a certain risk to reach into OS structures like that and relying on a given layout. Microsoft seems to be aware that this is being used by third parties, enough so to have kept the address fixed but making the region read-only in newer Windows versions (article linked above).

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.