Geoff Chappell, Software Analyst
The KUSER_SHARED_DATA structure defines the layout of a data area that the kernel places at a pre-set address for sharing with user-mode software. The original intention seems to have been to enable user-mode software to get frequently needed global data, such as the time, without the overhead of a trip to and from kernel mode. Of course, kernel-mode and user-mode access is through different addresses, and the user-mode address provides only for reading the data, not writing.
The pre-set address for access from kernel mode is defined symbolically in WDM.H as KI_USER_SHARED_DATA. It helps when debugging to remember that this is 0xFFDF0000 or 0xFFFFF780`00000000, respectively, in 32-bit and 64-bit Windows. Also defined is a convenient symbol, SharedUserData, which casts this constant address to a KUSER_SHARED_DATA pointer.
The read-only user-mode address for the shared data is 0x7FFE0000, both in 32-bit and 64-bit Windows. The only formal definition among headers in the WDK or the Software Development Kit (SDK) is in assembly language headers: KS386.INC from the WDK and KSAMD64.INC from the SDK both define MM_SHARED_USER_DATA_VA for the user-mode address (and USER_SHARED_DATA for the kernel-mode).
Though the KUSER_SHARED_DATA is not formally documented, a C-language definition has been available in NTDDK.H ever since the Device Driver Kit (DDK) for Windows 2000. It is tabulated here for two reasons. First, a reverse engineer of Windows is likely to encounter references to its members, as offsets from the pre-set address, and may usefully be spared from calculating the offsets of members from the formal definition.
Second, but having very much the greater importance, the structure changes but this is not tracked in Microsoft’s headers. Admittedly, the changes look to be close to inconsequential to user-mode code much above NTDLL. Close to inconsequential, however, is not ignorably inconsequential. The difference sometimes matters, and has even affected security. Cases exist where things have slipped into the KUSER_SHARED_DATA but might better not have been exposed so easily to user-mode software and notably not to malware. For instance, through many versions of 32-bit Windows before Windows 8, this structure’s involvement in user-mode calls to kernel mode had as a side-effect that all such calls go through one or two very predictable locations, thus greatly aiding software (sadly not limited just to malware) that seeks to intercept those calls. At first, this structure’s SystemCall member held the code to call. Later, all calls go through the exported NTDLL functions KiFastSystemCall and KiIntSystemCall after passing through SystemCall as a pointer. Another example that was removed for Windows 8 is that the protectiveness of Address Space Layout Randomization (ASLR), as far as it concerned predicting the run-time addresses of known sites in NTDLL, was reduced by this structure’s SystemDllNativeRelocation and SystemDllWowRelocation members. These are pretty serious blunders, especially given the context that both came about as implementation details for features that were announced at the time as increasing security. However much oversight and even outright mistakes are inevitable in system software, some record is better kept for history.
The KUSER_SHARED_DATA is unusual in that it has exactly the same layout for 32-bit and 64-bit Windows. This is because the one (global) instance is simultaneously accessible by both 32-bit and 64-bit user-mode code, and it’s desired that 32-bit user-mode code can run unchanged on both 32-bit and 64-bit Windows.
Large tracts of the structure do not change, or barely change, with the Windows version. Changes to the KUSER_SHARED_DATA have come mostly from growing at the end. But there have been changes within the structure, including to move members from one offset to another between builds, no matter that a comment in NTDDK.H says “The layout itself cannot change since this structure has been exported in ntddk, ntifs.h, and nthal.h for some time.” Presenting the structure over a range of versions is certainly not simple! The following sizes are known (with caveats that follow the table):
Version | Size |
---|---|
3.51 | 0x0238 |
early 4.0 (before Windows NT 4.0 SP3) | 0x02B4 |
mid 4.0 (Windows NT 4.0 SP3) | 0x02BC |
late 4.0 (Windows NT 4.0 SP4 and higher) | 0x02D4 |
5.0 | 0x02D8 |
early 5.1 (before Windows XP SP2) | 0x0320 |
late 5.1 (Windows XP SP2 and higher) | 0x0338 |
early 5.2 (before Windows Server 2003 SP1) | 0x0330 |
late 5.2 (Windows Server 2003 SP1 and higher) | 0x0378 |
6.0 | 0x03B8 |
6.1 to 6.3 | 0x05F0 |
10.0 | 0x0708 |
These sizes, and the offsets, types and names in the tables that follow, are from Microsoft’s symbol files for either or both of the kernel and NTDLL for Windows 2000 SP3 and higher, but are something of a guess for earlier versions since the symbol files for these do not contain type information for the KUSER_SHARED_DATA. What’s known of Microsoft’s names and types for earlier versions is instead inferred from what use NTOSKRNL, NTDLL and KERNEL32 are seen to make of the shared data at its known addresses. Even the size is not known with certainty in these versions since memory for the KUSER_SHARED_DATA is allocated (and zeroed) by the loader as a whole page.
Some variations are as simple as a change of type or name, as shown by the structure’s very first member. The ordinary-seeming Windows API function named GetTickCount used to be implemented as simply as a 64-bit multiplication of the volatile 32-bit TickCountLow (being the low 32 bits of the kernel’s 64-bit tick count) by the constant TickCountMultiplier and then a 64-bit shift right by 24 bits as a speedy way to convert from kernel-mode tick counts in whatever unit of measurement the kernel uses to user-mode tick counts in milliseconds. That the user-mode tick count can be read by executing a handful of instructions in user mode without asking the kernel is perhaps the original motivation of the shared data area. However, using only the low 32 bits of the kernel’s tick count means that the user-mode tick count in milliseconds resets to zero not only when it wraps around as a DWORD every 49 days or so but also whenever those low 32 bits of the kernel’s count wrap around. This second wrap-around is a whole extra problem, even if its real-world occurrence is made very much less likely by needing to leave Windows to run for approximately 2 years. When Microsoft got round to fixing it for Windows Server 2003, the KUSER_SHARED_DATA needed the whole of the kernel’s count, but as a new member at what was then the end of the structure—see offset 0x0320—not by replacing the old and moving anything else. Thus did TickCountLow turn to TickCountLowDeprecated.
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x00 |
ULONG volatile TickCountLow; |
3.51 to 5.1 | |
ULONG TickCountLowDeprecated; |
5.2 and higher | ||
0x04 |
ULONG TickCountMultiplier; |
3.51 and higher | |
0x08 |
KSYSTEM_TIME volatile InterruptTime; |
3.51 and higher | |
0x14 |
KSYSTEM_TIME volatile SystemTime; |
3.51 and higher | |
0x20 |
KSYSTEM_TIME volatile TimeZoneBias; |
3.51 and higher | |
0x2C |
USHORT ImageNumberLow; |
3.51 and higher | |
0x2E |
USHORT ImageNumberHigh; |
3.51 and higher | |
0x30 |
WCHAR NtSystemRoot [0x0104]; |
3.51 and higher | last member in 3.51 |
Much as the kernel’s updates of the TickCount are immediately available to the GetTickCount function, the kernel’s adjustments to the SystemTime are immediately available to the GetSystemTimeAsFileTime function.
Two additions for version 4.0 are used very differently from whatever might be imagined from the names that eventually turn up in symbol files. The names DriveMap and DriveType are proposed below because these are the names that Windows 2000 gives their re-implementations as members of the DEVICE_MAP structure. The kernel sets a bit in the DriveMap to indicate that the corresponding DOS drive is defined: 0 for A, 1 for B, etc, and that the drive type is set in the corresponding element of the DriveType array. In Windows NT 4.0, the KERNEL32 function GetLogicalDrives is nothing but a retrieval of the DriveMap from the KUSER_SHARED_DATA. The drive types are the same as returned by the KERNEL32 function GetDriveType.
Offset | Definition | Versions |
---|---|---|
0x0238 |
ULONG DriveMap; |
4.0 only |
ULONG MaxStackTraceDepth; |
5.0 and higher | |
0x023C |
ULONG CryptoExponent; |
4.0 and higher |
0x0240 |
ULONG TimeZoneId; |
4.0 and higher |
Learning a little about DOS drives without having to call the kernel may have seemed important once but it arguably was from the start an efficiency that was taken too far, and Windows 2000 did away with it. According to the symbol files, the DriveMap was soon reused (not that any use of its replacement, MaxStackTraceDepth, is yet known for any version) but the relatively substantial space taken by the DriveType array was left as reserved. It then shifts and shrinks as portions get redefined for use in Windows Server 2003 and then in Windows 8 until it disappears when fully used for Windows 10.
Offset | Definition | Versions |
---|---|---|
0x0244 |
UCHAR DriveType [0x20]; |
4.0 only |
ULONG Reserved2 [8]; |
5.0 to 5.1 | |
ULONG LargePageMinimum; |
5.2 and higher | |
0x0248 |
ULONG Reserved2 [7]; |
5.2 to 6.1 |
ULONG AitSamplingValue; |
6.2 and higher | |
0x024C |
ULONG AppCompatFlag; |
6.2 and higher |
0x0250 |
ULONGLONG RNGSeedVersion; |
6.2 and higher |
0x0258 |
ULONG GlobalValidationRunLevel; |
6.2 and higher |
0x025C |
LONG volatile TimeZoneBiasStamp; |
6.2 and higher |
0x0260 |
ULONG Reserved2; |
6.2 to 6.3 |
ULONG NtBuildNumber; |
10.0 and higher |
The remaining additions for version 4.0 are stable except that Windows 8 squeezed a new member into previously undefined space that had been left by an alignment requirement.
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x0264 |
NT_PRODUCT_TYPE NtProductType; |
4.0 and higher | |
0x0268 |
BOOLEAN ProductTypeIsValid; |
4.0 and higher | |
0x0269 |
UCHAR Reserved0 [1]; |
6.2 and higher | |
0x026A |
USHORT NativeProcessorArchitecture; |
6.2 and higher | |
0x026C |
ULONG NtMajorVersion; |
4.0 and higher | |
0x0270 |
ULONG NtMinorVersion; |
4.0 and higher | |
0x0274 |
UCHAR ProcessorFeatures [0x40]; |
4.0 and higher | last member in early 4.0 |
All known symbol files that define KUSER_SHARED_DATA have it that the members at offsets 0x02B4 and 0x02B8 are reserved. Perhaps they were at first named MmHighestUserAddress and MmSystemRangeStart, these being the names of the (internal) kernel variables they are initialised from. Reserved or not, the kernel continues to set them at least until Windows 10. Perhaps the NTDDK.H comment “do not use” is code for their being already in use.
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x02B4 |
ULONG Reserved1; |
mid 4.0 and higher | |
0x02B8 |
ULONG Reserved3; |
mid 4.0 and higher | last member in mid 4.0 |
No use is known of the next 0x14 bytes until Windows 2000. Space left by alignment after the AlternativeArchitecture got formally defined as padding in Windows 7 and eventually got used in Windows 10.
Offset | Definition | Versions |
---|---|---|
0x02BC | 0x14 bytes apparently unused | late 4.0 only |
ULONG volatile TimeSlip; |
5.0 and higher | |
0x02C0 |
ALTERNATIVE_ARCHITECTURE_TYPE AlternativeArchitecture; |
5.0 and higher |
0x02C4 |
ULONG AltArchitecturePad [1]; |
6.1 and higher |
ULONG BootId; |
10.0 and higher | |
0x02C8 |
LARGE_INTEGER SystemExpirationDate; |
5.0 and higher |
Late builds of Windows NT 4.0 set one byte of the SuiteMask, presumably because not enough product suites were yet supported for a second byte. The SuiteMask was introduced concurrently with the GetVersionEx function’s acceptance of an OSVERSIONINFOEX structure to fill. For that function filling that structure, the suite mask is the low 16 bits of 32 bits read from here.
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x02D0 |
ULONG SuiteMask; |
late 4.0 and higher | last member in late 4.0 |
Though all known C-language definitions of KdDebuggerEnabled in NTDDK.H have it as a BOOLEAN, it is in fact a pair of bit flags.
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x02D4 |
BOOLEAN KdDebuggerEnabled; |
5.0 and higher | last member in 5.0 |
The KUSER_SHARED_DATA ends at offset 0x02D8 in version 5.0, but the builds of version 5.1 starting with Windows XP SP2 and of version 5.2 starting with Windows Server 2003 SP1 define a byte in space left by alignment. This NXSupportPolicy can validly range only from 0x00 to 0x03. Windows 8 redefined it as a bit field and squeezed some more into the byte (and formally defined the rest of the space left by alignment as reserved).
Offset | Definition | Versions |
---|---|---|
0x02D5 |
UCHAR NXSupportPolicy; |
late 5.1 and late 5.2 to 6.1 |
union { UCHAR MitigationPolicies; struct { UCHAR NXSupportPolicy : 2; // 0x03 UCHAR SEHValidationPolicy : 2; // 0x0C UCHAR CurDirDevicesSkippedForDlls : 2; // 0x30 UCHAR Reserved : 2; }; }; |
6.2 and higher | |
0x02D6 |
UCHAR Reserved6 [2]; |
6.2 and higher |
Actual extension of the KUSER_SHARED_DATA for version 5.1 begins with a set of members that are retained forever.
Offset | Definition | Versions |
---|---|---|
0x02D8 |
ULONG volatile ActiveConsoleId; |
5.1 and higher |
0x02DC |
ULONG volatile DismountCount; |
5.1 and higher |
0x02E0 |
ULONG ComPlusPackage; |
5.1 and higher |
0x02E4 |
ULONG LastSystemRITEventTickCount; |
5.1 and higher |
0x02E8 |
ULONG NumberOfPhysicalPages; |
5.1 and higher |
0x02EC |
BOOLEAN SafeBootMode; |
5.1 and higher |
The ComPlusPackage member is effectively a process-wide cache of a registry value for the KERNEL32 function GetComPlusPackageInstallStatus to return to whoever’s interested. The kernel reads it as REG_DWORD data from the Enable64Bit value in HKEY_LOCAL_MACHINE\Software\Microsoft\.NETFramework, defaulting to zero. Microsoft documents 1 as COMPLUS_ENABLE_64BIT.
Version 6.1 found some use for more space left by alignment. By version 6.2 this was not nearly enough for what was wanted, and so the space returned to being reserved.
Offset | Definition | Versions |
---|---|---|
0x02ED |
union { UCHAR TscQpcData; struct { UCHAR TscQpcEnabled : 1; // 0x01 UCHAR TscQpcSpareFlag : 1; // 0x02 UCHAR TscQpcShift : 6; // 0xFC }; }; |
6.1 only |
UCHAR Reserved12 [3]; |
6.2 and higher | |
0x02EE |
UCHAR TscQpcPad [2]; |
6.1 only |
Version 5.1 defines one ULONG for TraceLogging but elaboration of support for tracing in version 6.0 made this member available for reuse as bit flags.
Offset | Definition | Versions |
---|---|---|
0x02F0 |
ULONG TraceLogging; |
5.1 to 5.2 |
union { ULONG SharedDataFlags; struct { /* slightly changing bit fields, see below */ }; }; |
6.0 and higher |
Version 6.1 continues its programme of formally defining padding that follows a member because of alignment.
Offset | Definition | Versions |
---|---|---|
0x02F4 |
ULONG DataFlagsPad [1]; |
6.1 and higher |
Use of the SYSENTER and SYSEXIT instructions for getting to and from kernel mode first had version 5.1 set aside enough space in the KUSER_SHARED_DATA for the kernel to assemble code there. A rethink when Windows XP SP2 and Windows Server 2003 SP1 introduced Data Execution Prevention (DEP) meant that much of this space returned to being unused.
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x02F8 |
ULONGLONG Fill0; |
early 5.1 and early 5.2 | |
ULONGLONG TestRetInstruction; |
late 5.1, late 5.2 and higher | ||
0x0300 |
ULONGLONG SystemCall [4]; |
early 5.1 and early 5.2 | last member in early 5.1 |
ULONG SystemCall; |
late 5.1 and late 5.2 to 6.1 | ||
LONGLONG QpcFrequency; |
6.2 and higher | ||
0x0304 |
ULONG SystemCallReturn; |
late 5.1 and late 5.2 to 6.1 | |
0x0308 |
ULONGLONG SystemCallPad [3]; |
late 5.1, late 5.2 and higher |
The build of version 5.1 for Windows XP SP2 picks up the new TickCount that was added chronologically earlier for version 5.2 to fix a defect in the arithmetic of the GetTickCount function. However, this new tick count at offset 0x0320 has no known use in any build of version 5.1. It appears to be in the definition, as known from the symbol files for Windows XP SP2 and SP3, only because it’s on the way to the Cookie, which was introduced jointly for Windows XP SP2 and the version 5.2 for Windows Server 2003 SP1 to support the EncodeSystemPointer and DecodeSystemPointer functions.
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x0320 |
union { KSYSTEM_TIME volatile TickCount; ULONGLONG volatile TickCountQuad; struct { // 6.1 and higher ULONG ReservedTickCountOverlay [3]; ULONG TickCountPad [1]; }; }; |
late 5.1 and higher | last member in early 5.2 |
0x0330 |
ULONG Cookie; |
late 5.1, late 5.2 and higher | last member in late 5.1 |
No build of version 5.1 continues the KUSER_SHARED_DATA beyond the structure’s 8-byte alignment after the Cookie. The version 5.2 from Windows Server 2003 SP1 uses the alignment space to start a relatively large array of Wow64SharedInformation. When Windows Vista inserted the ConsoleSessionForegroundProcessId ahead of that array, it created the first example of a member that changes offsets between versions. The Wow64SharedInformation was reassigned for Windows 8, defining nine new members and a reservation. Windows 10 deleted two of the new members, thus creating two more examples of members that shift between versions, and started using the reservation.
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x0334 |
ULONG CookiePad [1]; |
6.1 and higher | |
0x0338 |
LONGLONG ConsoleSessionForegroundProcessId; |
6.0 and higher | |
0x0334 (late 5.2); 0x0340 (6.0 to 6.1) |
ULONG Wow64SharedInformation [0x10]; |
late 5.2 to 6.1 | last member in late 5.2 |
ULONGLONG volatile TimeUpdateSequence; |
6.2 only | ||
ULONGLONG TimeUpdateLock; |
6.3 and higher | ||
0x0348 |
ULONGLONG BaselineSystemTimeQpc; |
6.2 and higher | |
0x0350 |
ULONGLONG BaselineInterruptTimeQpc; |
6.2 and higher | |
0x0358 |
ULONGLONG QpcSystemTimeIncrement; |
6.2 and higher | |
0x0360 |
ULONGLONG QpcInterruptTimeIncrement; |
6.2 and higher | |
0x0368 (6.2 to 6.3) |
ULONG QpcSystemTimeIncrement32; |
6.2 to 6.3 | |
0x036C (6.2 to 6.3) |
ULONG QpcInterruptTimeIncrement32; |
6.2 to 6.3 | |
0x0370 (6.2 to 6.3); 0x0368 |
UCHAR QpcSystemTimeIncrementShift; |
6.2 and higher | |
0x0371 (6.2 to 6.3); 0x0369 |
UCHAR QpcInterruptTimeIncrementShift; |
6.2 and higher | |
0x0372 (6.2 to 6.3); 0x036A |
UCHAR Reserved8 [0x0E]; |
6.2 to 6.3 | |
USHORT UnparkedProcessorCount; |
10.0 and higher | ||
0x036C |
UCHAR Reserved8 [0x14]; |
early 10.0 only | |
ULONG EnclaveFeatureMask [4]; |
10.0 version 1511, and higher | ||
0x037C |
ULONG Reserved8; |
10.0 version 1511, and higher |
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x0380 |
USHORT UserModeGlobalLogger [8]; |
6.0 only | |
USHORT UserModeGlobalLogger [0x10]; |
6.1 and higher | ||
0x0390 |
ULONG HeapTracingPid [2]; |
6.0 only | |
0x0398 |
ULONG CritSecTracingPid [2]; |
6.0 only | |
0x03A0 |
ULONG ImageFileExecutionOptions; |
6.0 and higher | |
0x03A4 |
ULONG LangGenerationCount; |
6.1 and higher | |
0x03A8 |
union { ULONGLONG AffinityPad; ULONG ActiveProcessorAffinity; }; |
6.0 only | |
ULONGLONG Reserved5; |
6.1 only | ||
ULONGLONG Reserved4; |
6.2 and higher | ||
0x03B0 |
ULONGLONG volatile InterruptTimeBias; |
6.0 and higher | last member in 6.0 |
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x03B8 |
ULONGLONG volatile TscQpcBias; |
6.1 to 6.2 | |
ULONGLONG volatile QpcBias; |
6.3 and higher | ||
0x03C0 |
ULONG volatile ActiveProcessorCount; |
6.1 to 6.3 | |
ULONG ActiveProcessorCount; |
10.0 and higher | ||
0x03C4 |
USHORT volatile ActiveGroupCount; |
6.1 only | |
UCHAR volatile ActiveGroupCount; |
6.2 and higher | ||
0x03C5 |
UCHAR Reserved9; |
6.2 and higher | |
0x03C6 |
USHORT Reserved4; |
6.1 only | |
union { USHORT TscQpcData; UCHAR volatile TscQpcEnabled; }; |
6.2 only | ||
union { USHORT QpcData; UCHAR volatile QpcBypassEnabled; }; |
6.3 and higher | ||
0x03C7 |
UCHAR TscQpcShift; |
6.2 ony | |
UCHAR QpcShift; |
6.3 and higher | ||
0x03C8 |
ULONG volatile AitSamplingValue; |
6.1 only | |
LARGE_INTEGER TimeZoneBiasEffectiveStart; |
6.2 and higher | ||
0x03CC |
ULONG volatile AppCompatFlag; |
6.1 only | |
0x03D0 |
ULONGLONG SystemDllNativeRelocation; |
6.1 only | |
LARGE_INTEGER TimeZoneBiasEffectiveEnd; |
6.2 and higher | ||
0x03D8 (6.1) |
ULONG SystemDllWowRelocation; |
6.1 only | |
0x03DC (6.1) |
ULONG XStatePad [1]; |
6.1 only | |
0x03E0 (6.1); 0x03D8 |
XSTATE_CONFIGURATION XState; |
6.1 and higher | last member in 6.1 and higher |
All growth of the KUSER_SHARED_DATA since Windows 8 is a side-effect of changes within the XSTATE_CONFIGURATION.
Windows Vista introduced bit fields at offset 0x02F0. Not only have most subsequent versions added bits but Windows 8 redefined two of them.
Mask | Definition | Versions |
---|---|---|
0x00000001 |
ULONG DbgErrorPortPresent : 1; |
6.0 and higher |
0x00000002 |
ULONG DbgElevationEnabled : 1; |
6.0 and higher |
0x00000004 |
ULONG DbgVirtEnabled : 1; |
6.0 and higher |
0x00000008 |
ULONG DbgInstallerDetectEnabled : 1; |
6.0 and higher |
0x00000010 |
ULONG DbgSystemDllRelocated : 1; |
6.0 to 6.1 |
ULONG DbgLkgEnabled : 1; |
6.2 and higher | |
0x00000020 |
ULONG DbgDynProcessorEnabled : 1; |
6.1 and higher |
0x00000040 |
DbgSEHValidationEnabled : 1; |
6.1 only |
ULONG DbgConsoleBrokerEnabled : 1; |
6.2 and higher | |
0x00000080 |
ULONG DbgSecureBootEnabled : 1; |
6.2 and higher |
0x00000100 |
ULONG DbgMultiSessionSku : 1; |
10.0 and higher |
ULONG SpareBits : 27; |
6.0 to 6.1 | |
ULONG SpareBits : 25; |
6.1 only | |
ULONG SpareBits : 24; |
6.2 to 6.3 | |
ULONG SpareBits : 23; |
10.0 and higher |