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, notably the time, without the overhead of calling 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.
In all Windows versions since the KUSER_SHARED_DATA structure’s introduction for version 3.50, the structure’s addresses are pre-set. Even the kernel begins its execution with a page set aside (by the loader) for this shared data. The kernel could move it, treating the initial address as just a detail of the kernel’s interface with the loader, but does not and might never be able to even in future versions because of considerations for backwards compatibility. Not only is the kernel-mode address published but its extant use has been encouraged through macros. Notably, though the x86 kernel exports KeQueryTickCount, KeQuerySystemTime and KeQueryInterruptTime as functions, all of them are defined as macros for x64 drivers. Very many drivers in active use have this address in their code.
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 an appropriately typed pointer:
#define SharedUserData ((KUSER_SHARED_DATA * const) KI_USER_SHARED_DATA)
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 Windows Driver Kit (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. That they also define USER_SHARED_DATA for the kernel-mode address suggests that they too are intended for kernel-mode programming, albeit of a sort that is at least aware of what address works for user-mode access.
Formal support that is unambiguously for user-mode access is published only for the ARM processors. For good or bad, these processors are ordinarily ignored for this study of Windows. A C-language header named NTARM.H which Microsoft published in some editions of the WDK for Windows 10 defines both MM_SHARED_USER_DATA_VA and USER_SHARED_DATA for the user-mode address. The latter is, like SharedUserData, convenient for being appropriately typed. Neither definition in this ARM-specific header is any immediate help for x86 and x64 programming. Headers that correspond to NTARM.H but for x86 and x64 programming seem never to have been published by Microsoft. They certainly do exist, though: private symbol files for such user-mode components as URLMON.DLL have been supplied in packages of public symbols since as long ago as Windows 8 and confirm the existence of headers named NTI386.H and NTAMD64.H. Inasmuch as they are the x86 and x64 correspondents to the published NTARM.H, it’s a sound inference that these too define MM_SHARED_USER_DATA_VA and USER_SHARED_DATA for the user-mode address.
The much reduced exposure of the user-mode address is presumably because the intended use is by low-level modules of Microsoft’s to support API functions. All higher-level user-mode software, if well written, would call these API functions, certainly if documented, rather than inspect the KUSER_SHARED_DATA directly.
The KUSER_SHARED_DATA structure is documented, but only as a very recent development. Microsoft’s own date is 19th August 2019 and I see no reason to disbelieve it. To say it’s documented is anyway a stretch. What was yet provided when I looked on 20th October 2020 was nothing but a skeleton: a C-language definition and a list of members with no explanation. Looking again two years later, on 14th October 2022, I see that Microsoft has since fleshed out the documentation by transferring comments from the published C-language definition: it’s better than nothing.
A C-language definition of the KUSER_SHARED_DATA has been available in NTDDK.H ever since the Device Driver Kit (DDK) for Windows 2000. Even through the decades during which the structure was not documented, it was well-known to kernel-mode programmers and was arguably the best-known of all undocumented Windows structures, so much so that it might easily not have the attention of a website that attempts to document the undocumented. That it is tabulated here is because the programmer who debugs low-level Windows code (or, not so very different, the reverse engineer who studies Windows) is likely to encounter references to this structure’s members, typically with hard-coded addresses, and may face two problems.
One is that however well circulated may be a C-language definition, it is arguably not documentation even if supported by exhaustive commenting, which the KUSER_SHARED_DATA definition is not. What documentation Microsoft has since published is no more—or, more recently, little more—than a placeholder, the sort of thing that is presented for no more effect than to have something to point to as new openness by a new Microsoft. What alternative I can offer must for years yet, if not forever, be scrappy for most of the structure. Still, it can’t help but help.
The bigger and perhaps more lasting reason is that the structure’s changes between (and even within) Windows versions are not tracked in Microsoft’s headers. Admittedly, the changes look to be close to inconsequential to higher-level user-mode code—indeed, to any 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 by others or to make their own calls with reduced likelihood of detection. At first, this structure’s SystemCall member held the code to call. Later, all calls to kernel mode 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.
Examples such as 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. Or so I’ve argued throughout my career, and thus does documenting the KUSER_SHARED_DATA look compelling.
Among relatively large structures, the KUSER_SHARED_DATA is highly unusual for having exactly the same layout in 32-bit and 64-bit Windows. This is because the one instance must be simultaneously accessible by both 32-bit and 64-bit code on 64-bit Windows, 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 either do not change or barely change between Windows versions. Changes to the KUSER_SHARED_DATA have come mostly from growing at the end. Yet 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.” The reality is that the structure has changed enough that its presentation over a range of versions is certainly not simple!
The following sizes are known (with caveats that follow the table):
Version | Size |
---|---|
3.50 | 0x2C |
3.51 | 0x0238 |
early 4.0 (before SP3) | 0x02B4 |
mid 4.0 (SP3) | 0x02BC |
late 4.0 (SP4 and higher) | 0x02D4 |
5.0 | 0x02D8 |
early 5.1 (before SP2) | 0x0320 |
late 5.1 (SP2 and higher) | 0x0338 |
early 5.2 (before SP1) | 0x0330 |
late 5.2 (SP1 and higher) | 0x0378 |
6.0 | 0x03B8 |
6.1 to 6.3 | 0x05F0 |
10.0 to 1903 | 0x0708 |
2004 | 0x0720 |
These sizes, and the offsets, types and names in the tables that follow, are from published headers for Windows 2000 and higher, supported by type information from Microsoft’s public symbol files for either or both of the kernel and NTDLL for Windows 2000 SP3 and higher. An early form of this type information somehow found its way into two statically linked libraries that Microsoft published with the DDKs for Windows NT 3.51 and Windows NT 4.0. Otherwise, what’s known of Microsoft’s names and types for early versions is instead inferred from what use the NTOSKRNL, NTDLL, KERNEL32 and other binaries are seen to make of the shared data at its known addresses. For version 3.50, which predates the availability of type information, even the structure’s size is not known with certainty since memory for the KUSER_SHARED_DATA is allocated (and zeroed) by the loader as a whole page, i.e., with no record in the code to show how much of the page is intended for the structure.
Some variations are as simple as a change of type or name, as shown by the structure’s very first member’s change from TickCountLow to TickCountLowDeprecated.
The ordinary-seeming Win32 API function named GetTickCount used to be implemented as simply as a 64-bit multiplication of the volatile 32-bit TickCountLow (being a copy of the low 32 bits of the kernel’s 64-bit tick count) by the constant TickCountMultiplier and then a 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 the expense of transitions to and from kernel mode 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 the 32-bit result of the conversion wraps around every 49 days or so but also whenever the input to the conversion, i.e., the low 32 bits of the kernel’s count, wraps 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.
The one wrap around is notorious but the other has hardly ever been mentioned, though it can’t have been unknown. To fix it, which did get Microsoft’s attention after nearly a decade, the KUSER_SHARED_DATA needed the whole 64 bits of the kernel’s count. Widening the 32-bit TickCountLow to 64 bits would have incompatibly shifted everything that follows. Instead, a new 64-bit TickCount got defined at what was then the end of the structure—see offset 0x0320—and the old 32-bit TickCountLow became unused space. Thus did TickCountLow become TickCountLowDeprecated:
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x00 |
ULONG volatile TickCountLow; |
3.50 to 5.1 | |
ULONG TickCountLowDeprecated; |
5.2 and higher | truly not used | |
0x04 |
ULONG TickCountMultiplier; |
3.50 and higher | |
0x08 |
KSYSTEM_TIME volatile InterruptTime; |
3.50 and higher | |
0x14 |
KSYSTEM_TIME volatile SystemTime; |
3.50 and higher | |
0x20 |
KSYSTEM_TIME volatile TimeZoneBias; |
3.50 and higher | last member in 3.50 |
It’s certainly no accident that the original KUSER_SHARED_DATA members all have something to do with time. Getting the time efficiently from user mode seems to have exercised Microsoft more than a little in the very early years of Windows. Concurrently with introducing the KUSER_SHARED_DATA for direct access to the kernel-mode tick count, as copied to TickCountLow, version 3.50 also dedicated an interrupt, number 0x2A, for getting the user-mode tick count more efficiently than does NtGetTickCount—still by calling the kernel but with much less overhead than going through interrupt 0x2E (which handles the generality of system calls, of which NtGetTickCount is just one). Curiously, though some of the other dedicated interrupt numbers from early versions have been repurposed, interrupt 0x2A survives in the x86 kernel even to Windows 10, yet I never have known of any user-mode caller in any version.
Whether the KUSER_SHARED_DATA has the whole 64-bit TickCount or just the 32-bit TickCountLow, the tick that’s counted is not actually an interrupt from any timer device. It is instead an abstraction of a timer interrupt. If only to me, programmers in general seem never to have taken in this distinction which may be long past due a few words of explanation.
To be aware of the passing of time without having to keep asking the time, the kernel (through the HAL) programs a timer device to interrupt periodically. The period should be small enough to meet expectations of precision, both by the kernel and by software in general, but not so small that overall responsiveness is degraded by the overhead of frequent interrupts. The period is allowed to vary, even at the direction of user-mode software that would like its times in milliseconds to be taken as exact. The minimum and maximum periods that are possible for the chosen clock are learnt from the HAL at initialisation and are then constant for all the rest of the kernel’s execution. No matter how the kernel varies the period, no matter what the frequency of actual interrupts, the kernel maintains the TickCount as if interrupts recur with the maximum period.
Since Windows 8.1, the kernel constrains the maximum period to be no larger than one sixty-fourth of a second, i.e., 15.625ms, which has become the typical length of a timer tick in ordinary experience—so much so that some programmers (or non-programming commentators) think it must be so.
As what was in version 3.50 the arguably best balance of efficiency between kernel-mode handling of timer interrupts and user-mode requests for the tick count in milliseconds, the TickCountLow and in later versions the 64-bit TickCount are raw counts of the kernel-mode ticks. Conversion to milliseconds is done only when someone wants it, as from calling GetTickCount or GetTickCount64.
The conversion is to multiply the raw tick count by the maximum period, now understood as the length of a tick in units of 100ns, and divide by 10,000. So that the GetTickCount functions can be faster for avoiding the division, the kernel precomputes a multiplier and puts it in the KUSER_SHARED_DATA too. This (constant) TickCountMultiplier is the maximum period, zero-extended to 64 bits, shifted left by 24 bits, and then divided by 10,000. Each conversion of the TickCount to milliseconds is then a multiplication by TickCountMultiplier and a shift right by 24 bits.
Astonishingly, this straightforward precomputation of the multiplier which many would expect is done in one C statement is instead done as its own routine of most likely ten C statements. This routine, named ExComputeTickCountMultiplier, looks to have been retained without change for something like 30 years. I nominate it as The Oldest Unchanged Kernel Code. Moreover, it is not dead code: it executes every time Windows starts.
Not that I mean to recommend it for programming, but from knowing how the TickCountMultiplier is computed you can learn the maximum period without calling the kernel: multiply the TickCountMultiplier by 10,000, round up to the next multiple of 2 to the power of 24, and shift right by 24 bits. For instance, the commonly observed multipliers 0x0FA00000 and 0x0F99A027 correspond respectively to maximum periods of 156,250 and 156,001 (remember, in units of 100ns).
This notion of the timer tick as an idealised interrupt with a period that’s constant through all of the kernel’s execution dates from version 3.50. It developed naturally enough from version 3.10, which has no flexibility for the timer interrupt’s period: the timer tick in version 3.10 truly is the interrupt. If modern documentation seems not to distinguish the timer tick from the actual timer interrupt, then at least some explanation is that there once was nothing to distinguish. See, for instance, that the headline text for the kernel-mode KeQueryTickCount function even today, 20th October 2020, reads “maintains a count of the interval timer interrupts that have occurred since the system was booted”. This description is exactly unchanged, word for word, since the function’s documentation in the DDK for Windows NT 3.1 in 1993, when it was correct. To unpick this, I can’t think of a better way than to follow the history.
In version 3.10, the kernel keeps a tick count and a system time as internal variables. The tick count starts at zero and literally is just a count of interrupts, starting from whenever it was during the kernel’s initialisation that whatever serves as the timer started interrupting. The system time is intended to relate to the outside world, as the time since the start of 1601 in units of 100ns. Its starting value is learnt from the HAL during the kernel’s initialisation, but it can then be changed through interfaces, including from user mode with sufficient privilege. The timer interrupts keep recurring with a constant period. Each increases the tick count by 1 and the system time by the period in units of 100ns.
In version 3.50, the kernel still keeps the tick count as a variable in its own data, though now exported (as KeTickCount) and widened (not just to 64 bits but extended into a KSYSTEM_TIME structure). What’s new for the tick count is that the kernel also maintains a copy of the low 32 bits as TickCountLow in the KUSER_SHARED_DATA for easy access from user mode. The system time in version 3.50 is no longer an internal variable in the kernel. Instead, the kernel keeps an interrupt time and a system time in the KUSER_SHARED_DATA as InterruptTime and SystemTime. The interrupt time is new. It’s like the tick count in that it starts as zero but is like the system time in having 100ns as its unit of measurement. Also new is that the time between interrupts can vary. They are still periodic but the period can be reprogrammed between a minimum and maximum which are constant. Each interrupt increases the InterruptTime by the current period in units of 100ns. If on an interrupt’s occurrence, the kernel sees that an idealised interrupt with the maximum period would have occurred since the last actual interrupt, then it increases the TickCount by 1 and the SystemTime by the maximum period in units of 100ns.
The scheme has been elaborated through the decades, but as far as concerns these first few members of the KUSER_SHARED_DATA the essence from the early history still holds. The TickCount and InterruptTime start from zero during the kernel’s initialisation. The InterruptTime and the SystemTime are in units of 100ns. The InterruptTime is the kernel’s most recent notion of time as learnt from actual interrupts. The TickCount and SystemTime are maintained on the actual interrupts but the TickCount and, before Windows Vista, the SystemTime are updated only as if interrupts have the maximum period.
Although what’s wanted of the TickCount, InterruptTime and SystemTime is 64 bits each, they are stored as 12-byte KSYSTEM_TIME structures. This is an x86 consideration, but it applies also to the x64 kernel through its support for user-mode software that executes the x86 instruction set. The x86 cannot read or write 64 bits in one instruction without a lock prefix, which would better be avoided, and anyway the instruction (cmpxchg8b) that allows this was not even available to the earliest versions. From the start, then, an efficient defence is needed against intermingled reads and writes. The defence can be highly specialised because of the tight control of the reading and writing. Only the kernel will ever write these members. The readers are more varied but are few and are ideally written only by Microsoft. Most importantly, the nature of the writes is that the high dword changes much less frequently than the low. For the members that ordinarily only ever increase as a time measured in units of 100ns, the high dword changes a little less than every 7 minutes and its old values can never recur in tens of thousands of years. The only change that needs defence is of the high dword. The design of the KSYSTEM_TIME is that the 64-bit value is followed by a duplicate of its high part. The kernel always writes the second high part and only then the usual low and high parts. Except for the kernel itself when it knows it cannot be interrupted, readers of the 64-bit value follow by reading the second high part and checking for equality with the first: if they differ, the reader knows to retry.
In practice, the reading is done only by Microsoft’s own low-level components which expose the results through interfaces. The function that well-behaved kernel-mode software calls for reading the SystemTime is KeQuerySystemTime. Curiously, no equivalent was provided for the InterruptTime until the version 5.0 kernel exports KeQueryInterruptTime.
The similarly direct user-mode interface for readng 64 bits of SystemTime is GetSystemTimeAsFileTime, which is exported from KERNEL32.DLL in version 3.51 and higher. Less direct but older is GetSystemTime, which reads the SystemTime but repackages it into a SYSTEMTIME structure. User-mode exposure of the InterruptTime through a documented function had to wait for QueryInterruptTime in version 10.0. The WINMM function timeGetTime reads the InterruptTime in version 3.50 and higher, but it does not return all 64 bits: it divides by 10,000 to report only whole milliseconds.
The real-world time described by the SystemTime is what was known in my childhood as Greenwich Mean Time (GMT) but was even then standardised as Coordinated Universal Time (UTC). Though it sometimes seems that American programmers, if not Americans generally, give little thought to a world that mostly writes the date differently, they do all know that their time of day is not that of a celebrated naval observatory not far outside London. What computer users everywhere want almost always is their own local time.
The TimeZoneBias is what must be subtracted from the system time to produce local time. The kernel learns it from the registry initially but it can be changed through interfaces, including from user mode. Though the bias is only ever specified as a whole number of minutes, the kernel sets it into the KUSER_SHARED_DATA as a number of 100ns units so that the subtraction from SystemTime is straightforward.
As with the TickCount, but unlike the InterruptTime and SystemTime, the TimeZoneBias in the KUSER_SHARED_DATA is just for user-mode access. It is a copy of a variable in the kernel’s own data. Kernel functions that work with the bias use the internal variable. No user-mode functions read it just to reveal it, only to use it for a subtraction or addition that converts to or from local time.
A possibly understated implication of the API functions’ dependence on the TimeZoneBias in the KUSER_SHARED_DATA for converting to and from local time is that local time is global for all processes.
Microsoft’s next choice of data to expose to user mode at fixed addresses gives our first example of KUSER_SHARED_DATA members that still have this exposure despite losing their known user-mode use:
Offset | Definition | Versions | Remarks |
---|---|---|---|
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 |
The x86 kernel sets both ImageNumberLow and ImageNumberHigh to IMAGE_FILE_MACHINE_I386 (0x014C) and the x64 kernel sets both to IMAGE_FILE_MACHINE_AMD64 (0x8664). At first, that was the whole of the kernel’s involvement with these members: they were set just to be read from user mode. When the KERNEL32 function CreateProcessW in versions 3.51 to 4.0 and then CreateProcessInternalW in versions 5.0 to 5.2 inspects the executable file that is proposed for the new process, it checks that the machine type from the file’s PE header lies between ImageNumberLow and ImageNumberHigh inclusive (or, in the wow64 builds only, is equal to IMAGE_FILE_MACHINE_I386). Version 6.0 moved this checking from KERNEL32 to the kernel: no user-mode use of these members is known in this or any later version.
The NtSystemRoot is the path to the Windows directory. Before version 3.51 it was discovered from user mode by calling the NtQuerySystemInformation function and giving SystemPathInformation (0x04) as the information class. That the path could instead be read from shared memory was sufficiently important that for two versions more, up to and including 5.0, calling the function to get the path got a break to the kernel-mode debugger to tell the programmer
EX: SystemPathInformation now available via SharedUserData
Two additions for version 4.0 were re-implemented by Windows 2000 as the DriveMap and DriveType members of the DEVICE_MAP structure. Microsoft’s names for them in the KUSER_SHARED_DATA are preserved for archaeologists as type information in the SHELL32 import library from the DDK for Windows NT 4.0. The kernel sets a bit in the DosDeviceMap to indicate that the corresponding DOS drive is defined (bit 0 for A, 1 for B, etc.) and that the drive type is set in the corresponding element of the DosDeviceDriveType array. In Windows NT 4.0, the KERNEL32 function GetLogicalDrives is nothing but a retrieval of the DosDeviceMap from the KUSER_SHARED_DATA. The drive types are the same as returned by the KERNEL32 function GetDriveType.
Offset | Definition | Versions |
---|---|---|
0x0238 |
ULONG DosDeviceMap; |
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. For Windows 2000, with its introduction of local DOS devices, the intrinsically global nature of the KUSER_SHARED_DATA changed the DosDeviceMap and DosDeviceDriverType from (extravagant) efficiency to impediment, and both were done away with. The DosDeviceMap was soon repurposed (not that any use of its replacement, MaxStackTraceDepth, is yet known for any version) but the relatively substantial space taken by the DosDeviceDriveType 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 | Remarks |
---|---|---|---|
0x0244 |
ULONG LargePageMinimum; |
5.2 and higher | |
0x0248 |
ULONG AitSamplingValue; |
6.2 and higher | previously ULONG volatile at 0x03C8 |
0x024C |
ULONG AppCompatFlag; |
6.2 and higher | previously ULONG volatile at 0x03CC |
0x0250 |
ULONGLONG RNGSeedVersion; |
6.2 and higher | |
0x0258 |
ULONG GlobalValidationRunLevel; |
6.2 and higher | |
0x025C |
LONG volatile TimeZoneBiasStamp; |
6.2 and higher | |
0x0244 (4.0 to 5.1); 0x0248 (5.2 to 6.1); 0x0260 |
UCHAR DosDeviceDriveType [0x20]; |
4.0 only | |
ULONG Reserved2 [8]; |
5.0 to 5.1 | ||
ULONG Reserved2 [7]; |
5.2 to 6.1 | ||
ULONG Reserved2; |
6.2 to 6.3 | ||
ULONG NtBuildNumber; |
10.0 and higher |
By redefining what remained of Reserved2 as the NtBuildNumber, Windows 10 fleshed out an area (below) that version 4.0 added for easy, well-defined information about the kernel and the processors that it runs on. This addition for version 4.0 has been 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 |
BOOLEAN 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 |
BOOLEAN ProcessorFeatures [0x40]; |
4.0 and higher | last member in early 4.0 |
The ProcessorFeatures array supports the documented kernel-mode and user-mode functions ExIsProcessorFeaturePresent and IsProcessorFeaturePresent. These functions provide an abstracted report of which processor features the kernel has enabled for use.
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 kernel variables that they are initialised from. Both variables were introduced for Windows NT 4.0 SP3 in conjunction with the BOOT.INI switch, named /3GB, that enabled the expansion of the user address space from 2GB to 3GB.
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 of Reserved1 to learn the highest user address is yet known. This boundary had long been discoverable from user mode as the MaximumUserModeAddress member of the SYSTEM_BASIC_INFORMATION, such as filled in by the NtQuerySystemInformation function when given SystemBasicInformation (0) as the information class.
In contrast, Reserved3 did see action. Where earlier versions of the KERNEL32 functions GlobalLock and LocalLock reject a handle for being a system address, they compare against the hard-coded 0x80000000. In the applicable Windows NT 4.0 service packs, they compare against Reserved3 instead. This didn’t last long, however. The lowest system address becomes discoverable through the SystemRangeStartInformation (0x32) information class in version 5.0. KERNEL32 changes to this in version 5.0 and no use of Reserved3 to learn the lowest system address is known in any later version. It seems at least plausible that the name Reserved3 actually dates from then. This has the merit of possibly explaining the numbering: Reserved1, with no user-mode use, was named first; Reserved2 and Reserved3 lost their user-mode use concurrently and were named in ascending order.
Reserved or not, both the preceding members are still set by the kernel at least until the 2004 release of Windows 10.
Offset | Definition | Versions |
---|---|---|
0x02BC |
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 |
No use is known of the preceding 0x14 bytes until Windows 2000. Space that the 8-byte alignment of SystemExpirationDate leaves after AlternativeArchitecture got formally defined as padding in Windows 7 and eventually got used in Windows 10.
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x02BC (late 4.0) | unaccounted 0x14 bytes | late 4.0 only | |
0x02D0 |
ULONG SuiteMask; |
late 4.0 and higher | last member in late 4.0 |
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.
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 last three bytes are just for alignment and are undefined. One byte in this space then got defined by the builds of version 5.1 starting with Windows XP SP2 and of version 5.2 starting with Windows Server 2003 SP1. This NXSupportPolicy can validly range only from 0x00 to 0x03. Windows 8 redefined it as a two-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; late 5.2 to 6.1 |
union { UCHAR MitigationPolicies; struct { /* bit fields, follow link */ }; }; |
6.2 and higher | |
0x02D6 |
UCHAR Reserved6 [2]; |
6.2 to 1809 |
USHORT CyclesPerYield; |
1903 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 caches 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 | Remarks |
---|---|---|---|
0x02ED |
union { UCHAR TscQpcData; struct { UCHAR TscQpcEnabled : 1; // 0x01 UCHAR TscQpcSpareFlag : 1; // 0x02 UCHAR TscQpcShift : 6; // 0xFC }; }; |
6.1 only | next as 16-bit union at 0x03C6 |
UCHAR VirtualizationFlags; |
1607 and higher | ||
0x02EE (6.1); 0x02ED (6.2 to 1511); 0x02EE |
UCHAR TscQpcPad [2]; |
6.1 only | |
UCHAR Reserved12 [3]; |
6.2 to 1511 | ||
UCHAR Reserved12 [2]; |
1607 and higher |
Version 5.1 defines one ULONG for TraceLogging but elaboration of support for tracing in version 6.0 made this member available for reuse:
Offset | Definition | Versions |
---|---|---|
0x02F0 |
ULONG TraceLogging; |
5.1 to 5.2 |
union { ULONG SharedDataFlags; struct { /* slightly changing bit fields, follow link */ }; }; |
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 the 32-byte SystemCall (below) as space in which the kernel assembles code. 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; early 5.2 |
|
ULONGLONG TestRetInstruction; |
late 5.1; late 5.2 and higher |
||
0x0300 |
ULONGLONG SystemCall [4]; |
early 5.1; early 5.2 |
last member in early 5.1 |
ULONG SystemCall; |
late 5.1; late 5.2 to 6.1 |
||
LONGLONG QpcFrequency; |
6.2 and higher | ||
0x0304 (late 5.1, late 5.2 to 6.1) |
ULONG SystemCallReturn; |
late 5.1; late 5.2 to 6.1 |
|
0x0308 |
ULONG SystemCall; |
1511 and higher | |
0x030C |
ULONG SystemCallPad0; |
1511 to 1903 | |
union { ULONG AllFlags; struct { ULONG Win32Process : 1; ULONG Sgx2Enclave : 1; ULONG VbsBasicEnclave : 1; ULONG SpareBits : 29; }; } UserCetAvailableEnvironments; |
2004 and higher | ||
0x0308 (late 5.1, late 5.2 to 10.0); 0x0310 |
ULONGLONG SystemCallPad [3]; |
late 5.1; late 5.2 to 10.0 |
|
ULONGLONG SystemCallPad [2]; |
1511 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; ULONG64 volatile TickCountQuad; }; |
late 5.1 to 6.0 | last member in early 5.2 |
union { KSYSTEM_TIME volatile TickCount; ULONG64 volatile TickCountQuad; struct { ULONG ReservedTickCountOverlay [3]; ULONG TickCountPad [1]; }; }; |
6.1 and higher | ||
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 defined member (i.e., not reserved or for padding) 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 |
0x0340 |
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 | |
0x036A |
USHORT UnparkedProcessorCount; |
10.0 and higher | |
0x036C |
ULONG EnclaveFeatureMask [4]; |
1511 and higher | |
0x037C |
ULONG TelemetryCoverageRound; |
1709 and higher | |
0x0372 (6.2 to 6.3); 0x036C (10.0; 0x037C (1511 to 1703) |
UCHAR Reserved8 [0x0E]; |
6.2 to 6.3 | |
UCHAR Reserved8 [0x14]; |
10.0 only | ||
ULONG Reserved8; |
1511 to 1703 |
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x0380 |
USHORT UserModeGlobalLogger [8]; |
6.0 only | |
USHORT UserModeGlobalLogger [16]; |
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 |
Each element of the UserModeGlobalLogger array is a two-byte ETW_UMGL_KEY structure. A header named NTETW.H, which Microsoft is yet known to have published only in the WDK for the original and 1511 releases of Windows 10, gives not only a C-language definition of this small structure but also defines the indexing along with many macros for convenience. The following are thus known as Microsoft’s names for meaningful indexes into the UserModeGlobalLogger array:
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; struct { BOOLEAN volatile TscQpcEnabled; UCHAR TscQpcShift; }; }; |
6.2 only | previously 8-bit union at 0x02ED | |
union { USHORT QpcData; struct { BOOLEAN volatile QpcBypassEnabled; UCHAR QpcShift; }; }; |
6.3 to 1607 | ||
union { USHORT QpcData; struct { UCHAR volatile QpcBypassEnabled; UCHAR QpcShift; }; }; |
1709 and higher |
Version 1709 changes QpcBypassEnabled from a UCHAR that is intended to be either TRUE or FALSE to one whose meaning is taken in bits. The C-language definitions of KUSER_SHARED_DATA in the contemporaneous WDKs give Microsoft’s names for these bits:
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x03C8 (6.1) |
ULONG volatile AitSamplingValue; |
6.1 only | next without volatile at 0x0248 |
0x03CC (6.1) |
ULONG volatile AppCompatFlag; |
6.1 only | next without volatile at 0x024C |
0x03D0 (6.1) |
ULONGLONG SystemDllNativeRelocation; |
6.1 only | |
0x03D8 (6.1) |
ULONG SystemDllWowRelocation; |
6.1 only | |
0x03DC (6.1) |
ULONG XStatePad [1]; |
6.1 only | |
0x03C8 |
LARGE_INTEGER TimeZoneBiasEffectiveStart; |
6.2 and higher | |
0x03D0 |
LARGE_INTEGER TimeZoneBiasEffectiveEnd; |
6.2 and higher | |
0x03E0 (6.1); 0x03D8 |
XSTATE_CONFIGURATION XState; |
6.1 and higher | last member in 6.1 to 1903 |
Growth of the KUSER_SHARED_DATA for Windows 10 was a side-effect of changes within the XSTATE_CONFIGURATION at the end. Not until the 2004 edition does the KUSER_SHARED_DATA itself get anything new at its end:
Offset | Definition | Versions | Remarks |
---|---|---|---|
0x0710 |
KSYSTEM_TIME FeatureConfigurationChangeStamp; |
2004 and higher | |
0x071C |
ULONG Spare; |
2004 and higher | last member in 2004 |
The FeatureConfigurationChangeStamp is the first new use that is known of the KSYSTEM_TIME structure anywhere in Windows since late builds of version 5.2 corrected the sharing of only a 32-bit TickCountLow and instead keeps the whole TickCount. This new time stamp looks like it’s provided by the kernel for user-mode access only. The kernel uses its own copy in an undocumented structure in the kernel’s own data. The NTDLL export RtlQueryFeatureConfigurationChangeStamp reads the value from the KUSER_SHARED_DATA. The same-named export from the kernel reads from the kernel’s internal variable.