Geoff Chappell - Software Analyst
The Process Environment Block (PEB) is a process’s user-mode representation. It has the highest-level knowledge of a process in kernel mode and the lowest-level in user mode. The PEB is created by the kernel but is mostly operated on from user mode. If a (system) process has no user-mode footprint, it has no PEB. If only in principle, if anything about a process is shared with kernel mode but can be properly managed in user mode without needing a transition to kernel mode, it goes in the PEB. If anything about a process might usefully be shared between user-mode modules, then it’s at least a candidate for going in the PEB for easy access. Very much more in principle than in practice, data may go into the PEB for sharing between processes more easily than by any formal inter-process communication.
User-mode code can easily find its own process’s PEB, albeit only by using undocumented or semi-documented behaviour. While a thread executes in user mode, its fs or gs register, for 32-bit and 64-bit code respectively, addresses the thread’s TEB. That structure’s ProcessEnvironmentBlock member holds the address of the current process’s PEB. In NTDLL version 5.1 and higher, this simple work is available more neatly as an exported function, named RtlGetCurrentPeb, but it too is undocumented. Its implementation is something very like
PEB *RtlGetCurrentPeb (VOID) { return NtCurrentTeb () -> ProcessEnvironmentBlock; }
For its own low-level user-mode programming, Microsoft has long had a macro or inlined routine, apparently named NtCurrentPeb, which reads directly from fs or gs, e.g.,
PEB *NtCurrentPeb (VOID) { return (PEB *) __readfsdword (FIELD_OFFSET (TEB, ProcessEnvironmentBlock)); }
The difference between an exported function (RtlGetCurrentPeb) and a macro or inlined routine (NtCurrentPeb) scarcely matters at run time but has forensic significance because use of the latter in a high-level module, e.g., for MSHTML.DLL from Internet Explorer 6, not only shows that Microsoft’s application programmers had undocumented knowledge of the PEB and TEB but also suggests they had access to otherwise private headers (if not to use them in their build, then at least to reproduce from them).
User-mode code can less easily access the PEB of any process for which it has a handle and sufficient access rights. The gatekeeper is the NtQueryInformationProcess function. This is exported by NTDLL in all known Windows versions. Its ProcessBasicInformation case fills a PROCESS_BASIC_INFORMATION structure whose member named PebBaseAddress is, unsurprisingly, the address of the queried process’s PEB. Of course, the address thus obtained is not directly usable. It is meaningful in the queried process’s address space. Even just to read that process’s PEB then requires such functions as ReadProcessMemory and the corresponding permission. To do much with what’s read may require synchronisation with or defence against changes being made by the queried process’s own threads—and writing to the queried process’s PEB certainly requires such synchronisation. In consequence, safe use of another process’s PEB is beyond many programers who attempt it, e.g., for malware and more notably for some of what gets foisted onto consumers as anti-malware or merely recommended to them as supposedly helpful system tools.
In an ideal world, the PEB might be opaque outside the kernel and a few low-level user-mode modules such as NTDLL and KERNEL32. But, as noted in remarks above about forsensic signfiicance, various high-level modules supplied with Windows over the years have used a few members of the PEB, and this eventually had to be disclosed. A new header, named WINTERNL.H, for previously internal APIs was added to the Software Development Kit (SDK) apparently in 2002 as the main (if insubstantial) outcome of an anti-trust settlement, and remains to this day. It originally presented a modified PEB that has just the BeingDebugged and SessionId members, plus padding that gets these members to the same offsets as in the true structure. More members have been included in this modified PEB over the years: Ldr, ProcessParameters and PostProcessInitRoutine in the SDK for Windows 7; and AtlThunkSListPtr and AtlThunkSListPtr32 in the SDK for Windows 8. Notwithstanding the header’s warnings, it seems unlikely that Microsoft will change the PEB in any way that moves any of these members.
Indeed, the PEB is highly stable across Windows versions. When members fall out of use the space they occupied tends to be left in place, often to be reused eventually, but without shifting other members. Many members that are useful—to know about not just when debugging but also when studying malware—have kept their positions through all the known history. The PEB has grown mostly by adding new members at its end. The following sizes are known (with caveats that follow the table):
Version | Size (x86) | Size (x64) |
---|---|---|
3.10 to 3.50 | 0x70 | |
3.51 | 0x98 | |
4.0 | 0x0150 | |
5.0 | 0x01E8 | |
5.1 | 0x0210 | |
5.2 | 0x0230 | 0x0358 |
6.0 | 0x0238 | 0x0368 |
6.1 | 0x0248 | 0x0380 |
6.2 to 10.0 | 0x0250 | 0x0388 |
1511 to 1703 | 0x0460 | 0x07A0 |
1709 | 0x0468 | 0x07B0 |
1803 | 0x0470 | 0x07B8 |
1809 to 2004 | 0x0480 | 0x07C8 |
These sizes, and the offsets, types and names in the tables that follow, are from Microsoft’s symbol files for the kernel starting with Windows 2000 SP3 and for NTDLL starting with Windows XP, but are something of a guess for earlier versions since the symbol files for these do not contain type information for the PEB. What’s known of Microsoft’s names and types for earlier versions is instead inferred from what use NTOSKRNL and various low-level user-mode modules such as NTDLL are seen to make of the PEB. Exhaustively tracking down all such use would be difficult, if not impossible, even with source code.
The very first member is arguably too much overlooked, given that so many programmers with backgrounds in Unix seem to think that assessment of Windows as an operating system begins and ends with whether Windows truly can fork a process. It is here thought to have been followed by unlabelled alignment space until version 3.51 defined the next two booleans.
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x00 | 0x00 |
BOOLEAN InheritedAddressSpace; |
all |
0x01 | 0x01 |
BOOLEAN ReadImageFileExecOptions; |
3.51 and higher |
0x02 | 0x02 |
BOOLEAN BeingDebugged; |
3.51 and higher |
0x03 | 0x03 |
BOOLEAN SpareBool; |
3.51 to early 5.2 |
union { UCHAR BitField; struct { /* bit fields, follow link */ }; }; |
late 5.2 and higher | ||
0x04 |
UCHAR Padding0 [4]; |
6.3 and higher | |
0x04 | 0x08 |
HANDLE Mutant; |
all |
These first eight bytes of the PEB have a separate identity as an INITIAL_PEB structure, apparently only for passing parameters to the kernel’s internal routine that creates a PEB. No trace of this INITIAL_PEB ever shows in public symbol files but it is known from the USERKDX debugger extension that Microsoft supplied with the Device Driver Kit (DDK) for Windows NT 4.0 and again for Windows 2000.
The kernel sets BeingDebugged to indicate that the process has a debug port. The (documented) KERNEL32 function IsDebuggerPresent does nothing more than read BeingDebugged from the current PEB.
Whether the byte at offset 0x03 was labelled explicitly as a spare boolean concurrently with definition of the two booleans at offsets 0x01 and 0x02 is not certain but is at least plausible. It anyway never was used as a boolean but started getting used as bit fields in the build of version 5.2 that first put the CPU’s support for large pages to use as an efficiency for executable images. The individual bits are presented separately, description being complicated because Windows 8.1 deleted one of them (IsLegacyProcess) and thus changed the masks for accessing the others.
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x08 | 0x10 |
PVOID ImageBaseAddress; |
all |
0x0C | 0x18 |
PEB_LDR_DATA *Ldr; |
all |
0x10 | 0x20 |
RTL_USER_PROCESS_PARAMETERS *ProcessParameters; |
all |
0x14 | 0x28 |
PVOID SubSystemData; |
all |
0x18 | 0x30 |
PVOID ProcessHeap; |
all |
Of the original PEB members, Ldr and ProcessParameters are arguably the most used by Microsoft’s higher-level modules and Microsoft eventually included them in the reduced PEB that’s published in WINTERNL.H for all the world to know about. In any world in which such publication had any self-consistency, the ProcessHeap wouldn’t be far behind: the ancient (documented) KERNEL32 function GetProcessHeap has always done nothing more than read ProcessHeap from the current PEB, but very many Microsoft programs and DLLs instead read ProcessHeap by themselves (as if GetProcessHeap is inlined for their use).
At the other extreme, the SubSystemData is about as obscure as anything gets in Windows programming for ordinary purposes. As its name suggests, it is intended for subsystems that don’t have enough of Microsoft’s attention to justify defining their own members in the PEB itself. A subsystem, such as supported by PSXDLL.DLL, can point SubSystemData at its own collection of per-process data.
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x1C | 0x38 |
PVOID FastPebLock; |
3.10 to 5.0 |
RTL_CRITICAL_SECTION *FastPebLock; |
5.1 and higher | ||
0x20 | 0x40 |
PVOID FastPebLockRoutine; |
3.10 to 5.1 |
PVOID SparePtr1; |
early 5.2 only | ||
PVOID AtlThunkSListPtr; |
late 5.2 and higher | ||
0x24 | 0x48 |
PVOID FastPebUnlockRoutine; |
3.10 to 5.1 |
PVOID SparePtr2; |
5.2 only | ||
PVOID IFEOKey; |
6.0 and higher |
In early versions, NTDLL supports its exported (undocumented) RtlAcquirePebLock and RtlReleasePebLock functions by storing in the PEB the addresses not just of a FastPebLock variable in the NTDLL data but of two routines for acquiring and releasing whatever is this lock. Though it does happen that the lock is a critical section and the routines are just the expected RtlEnterCriticalSection and RtlLeaveCriticalSection, not until version 5.1 is the lock’s nature formalised in the PEB and not until version 5.2 does NTDLL stop saving the routines’ addresses in the PEB.
You might wonder why they ever were saved in the PEB. After all, the RtlAcquirePebLock and RtlReleasePebLock functions ought to suffice for Microsoft’s user-mode code that’s outside NTDLL and wants to synchronise its access to the PEB with access by other threads in the same process. What fascinates me, and prompts this digression, is that the only use I know of FastPebLock from outside NTDLL is in kernel mode. Moreover, it also uses the long-gone FastPebLockRoutine and FastPebUnlockRoutine members. Go back far enough and this is done by linking the exact same implementations of the RtlAcquirePebLock and RtlReleasePebLock functions into both NTDLL and the kernel—yes, with the kernel finding the PEB from the TEB, in turn found from the fs register as described above. Version 5.1 re-implemented so that the kernel instead progresses through structures that have no user-mode susceptibility, thus from the fs register to the KPCR to the KTHREAD to the EPROCESS for its pointer to the PEB. If this change was motivated by thoughts of security, it was worse than pointless because the kernel does not just follow the FastPebLockRoutine and FastPebUnlockRoutine pointers in the PEB but calls through them to execute (what it hopes to be) NTDLL code at its user-mode address. Do not miss that whatever is there gets executed with ring 0 privilege.
This trick that is plainly too clever for anyone’s good was ended for version 5.2 in 2003, which surely is everyone’s gain, yet it was retained even for the long-lived last service pack of version 5.1 in 2008, apparently without Microsoft ever warning anyone of it. In the very earliest versions, it had extensive use. Among the reasons the kernel would access the PEB in ways that needed synchronisation with access by other threads (most likely in user mode) were such things as the kernel allocating from and freeing to the process heap. Even as late as version 5.1, this execution of user-mode code with kernel-mode prvilege was still being done for the exported (and documented) function RtlQueryRegistryValues to expand environment variables whose names are found between percent signs in registry data that has the REG_EXPAND_SZ type.
Can it really be that Microsoft was never called out for this grotesqueness?
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x28 | 0x50 |
ULONG EnvironmentUpdateCount; |
3.50 to 5.2 |
union { ULONG CrossProcessFlags; struct { /* bit fields, follow link */ }; }; |
6.0 and higher | ||
0x54 |
UCHAR Padding1 [4]; |
6.3 and higher | |
0x2C | 0x58 |
PVOID KernelCallbackTable; |
3.51 to 5.2 |
union { PVOID KernelCallbackTable; PVOID UserSharedInfoPtr; }; |
6.0 and higher | ||
0x28 (3.10); 0x2C (3.50) |
unaccounted 0x10 bytes | 3.10 only | |
unaccounted four bytes | 3.50 only | ||
0x30 |
ULONG SystemReserved [2]; |
5.0 only | |
0x60 |
ULONG SystemReserved [1]; |
5.1 to 1703 | |
ULONG SystemReserved; |
1709 and higher | ||
0x30 (3.50 to 4.0) |
HANDLE EventLogSection; |
3.50 to 4.0 | |
0x34 (3.50 to 4.0) |
PVOID EventLog; |
3.50 to 4.0 | |
0x34 |
struct { ULONG ExecuteOptions : 2; ULONG SpareBits : 30; }; |
early 5.1; early 5.2 |
|
0x64 |
ULONG SpareUlong; |
late 5.2 to 6.0 | |
ULONG AtlThunkSListPtr32; |
late 5.1; 6.1 and higher |
No use is known of the preceding 0x10 bytes in version 3.10. It seems more than merely plausible that the explicit reservation of two dwords as SystemReserved, as known from symbol files for late service packs of Windows 2000, started as four.
In those versions that have it, the EnvironmentUpdateCount is incremented when an attempt to set the current directory gets as far as NTDLL’s RtlSetCurrentDirectory_U function. What this has to do with any sort of environment is not known. Windows Vista anyway replaced this counter with a set of flags.
What KernelCallbackTable points to is an array of function pointers to support the exported (undocumented) KiUserCallbackDispatcher function. This is one of the relatively few functions that NTDLL exports not to be imported by other user-mode modules but to be found by the kernel. The function is called by the kernel when a driver, typically WIN32K.SYS, calls the kernel export KeUserModeCallback. Of course, the NTDLL function is not actually called by the kernel. It instead becomes the target address for the kernel’s exit from ring 0 to ring 3. Still, KiUserCallbackDispatcher perceives that it has been called and that among its arguments is an index into the KernelCallbackTable. This selects where further to dispatch the execution deeper into user mode. Getting back to kernel mode with the appearance of returning from a call to user mode is important enough to have a dedicated interrupt number, 0x2B.
The array of function pointers that is the KernelCallbackTable is set into place by USER32.DLL during its initialisation, but not until after USER32 connects to the CSRSS server. Starting with version 6.0, if the process is a so-called protected process, the KernelCallbackTable pointer is first put to double duty as the UserSharedInfoPtr. Just while connecting, it becomes a side-channel for receiving a SHAREDINFO structure directly from WIN32K.SYS.
Windows XP and Windows Server 2003 got into some sort of tussle about using the last of the previously reserved dwords. The ExecuteOptions certainly are used in the early releases of both. These two bits do not, however, have the same meaning as later flags for the Data Execution Prevention (DEP) that came with the late builds of these versions. They are concerned instead with checking for stack overflow.
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x38 | 0x68 |
PEB_FREE_BLOCK *FreeList; |
3.10 to early 6.0 |
ULONG SparePebPtr0; |
late 6.0 only | ||
PVOID ApiSetMap; |
6.1 and higher |
The PEB_FREE_BLOCK is simply a pointer to the Next of its type, presumably to make a single-linked list, and a 32-bit unsigned Size. The suggestion is of caching freed memory, but although FreeList is defined in symbol files, no use is known of it in any version. The ApiSetMap that replaces it is the process’s pointer to the kernel’s representation of the API Set Schema of redirections that NTDLL is to apply when loading DLLs. What the kernel points ApiSetMap to is a read-only mapping into the process’s address space. Pointing ApiSetMap elsewhere would seem to be not just possible but attractive, whether for mischief or for the supposedly well-intentioned intrusiveness of security tools as an alternative to hooking API functions by such techniques as patching code.
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x3C | 0x70 |
ULONG TlsExpansionCounter; |
all |
0x74 |
UCHAR Padding2 [4]; |
6.3 and higher | |
0x40 | 0x78 |
PVOID TlsBitmap; |
all |
0x44 | 0x80 |
ULONG TlsBitmapBits [2]; |
all |
0x4C | 0x88 |
PVOID ReadOnlySharedMemoryBase; |
all |
0x50 | 0x90 |
PVOID ReadOnlySharedMemoryHeap; |
3.10 to 5.2 |
PVOID HotpatchInformation; |
6.0 to 6.2 | ||
PVOID SparePvoid0; |
6.3 to 1607 | ||
PVOID SharedData; |
1703 and higher | ||
0x54 | 0x98 |
PVOID *ReadOnlyStaticServerData; |
all |
0x58 | 0xA0 |
PVOID AnsiCodePageData; |
all |
0x5C | 0xA8 |
PVOID OemCodePageData; |
all |
0x60 | 0xB0 |
PVOID UnicodeCaseTableData; |
all |
0x64 | 0xB8 |
ULONG NumberOfProcessors; |
3.51 and higher |
0x68 | 0xBC |
ULONG NtGlobalFlag; |
3.51 and higher |
0x68 (3.10 to 3.50); 0x70 |
0xC0 |
LARGE_INTEGER CriticalSectionTimeout; |
all |
The NtGlobalFlag member is intially the process’s copy of the kernel’s (exported) NtGlobalFlag variable as it was when the kernel created the PEB.
Before version 5.0, having an NtGlobalFlag in the PEB is nothing but a convenience for NTDLL to initialise its own (internal) NtGlobalFlag variable without having to call through NtQuerySystemInformation. It’s the internal variable that gets per-processor adjustments, e.g., from the GlobalFlag value in the Image File Execution Options or from applicable fields in the Configuration Directory. It’s the internal variable that matters. The NtGlobalFlag in the PEB can be just a stale record of what the process started with.
See that version 3.51 didn’t just append new members but instead inserted two. One was in what looks to have been unused alignment space, but the other turns CriticalSectionTimeout into the oldest known case of any PEB member shifting between versions.
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x78 | 0xC8 |
ULONG_PTR HeapSegmentReserve; |
3.51 and higher |
0x7C | 0xD0 |
ULONG_PTR HeapSegmentCommit; |
3.51 and higher |
0x80 | 0xD8 |
ULONG_PTR HeapDeCommitTotalFreeThreshold; |
3.51 and higher |
0x84 | 0xE0 |
ULONG_PTR HeapDeCommitFreeBlockThreshold; |
3.51 and higher |
0x88 | 0xE8 |
ULONG NumberOfHeaps; |
3.51 and higher |
0x8C | 0xEC |
ULONG MaximumNumberOfHeaps; |
3.51 and higher |
0x90 | 0xF0 |
PVOID *ProcessHeaps; |
3.51 and higher |
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x94 | 0xF8 |
PVOID GdiSharedHandleTable; |
3.51 and higher |
0x98 | 0x0100 |
PVOID ProcessStarterHelper; |
4.0 and higher |
0x9C | 0x0108 |
ULONG GdiDCAttributeList; |
4.0 and higher |
0x010C |
UCHAR Padding3 [4]; |
6.3 and higher | |
0xA0 | 0x0110 |
PVOID LoaderLock; |
4.0 to 5.1 |
RTL_CRITICAL_SECTION *LoaderLock; |
5.2 and higher | ||
0xA4 | 0x0118 |
ULONG OSMajorVersion; |
4.0 and higher |
0xA8 | 0x011C |
ULONG OSMinorVersion; |
4.0 and higher |
0xAC | 0x0120 |
USHORT OSBuildNumber; |
4.0 and higher |
0xAE | 0x0122 |
USHORT OSCSDVersion; |
4.0 and higher |
0xB0 | 0x0124 |
ULONG OSPlatformId; |
4.0 and higher |
0xB4 | 0x0128 |
ULONG ImageSubsystem; |
4.0 and higher |
0xB8 | 0x012C |
ULONG ImageSubsystemMajorVersion; |
4.0 and higher |
0xBC | 0x0130 |
ULONG ImageSubsystemMinorVersion; |
4.0 and higher |
0x0134 |
UCHAR Padding4 [4]; |
6.3 and higher | |
0xC0 | 0x0138 |
KAFFINITY ImageProcessAffinityMask; |
4.0 to early 6.0 |
KAFFINITY ActiveProcessAffinityMask; |
late 6.0 and higher | ||
0xC4 | 0x0140 |
ULONG GdiHandleBuffer [0x22]; |
4.0 and higher (x86) |
ULONG GdiHandleBuffer [0x3C]; |
all (x64) |
The point to the several members starting at OSMajorVersion is very much that they need not be truly the operating system’s version numbers. They can instead be whatever version numbers are meant to be perceived by user-mode code in the process. Whether this happens depends on the Win32VersionValue in the image header of the process’s executable. To this day, 30th March 2019, Microsoft’s documentation would have it that “this member is reserved and must be 0.” If, however, it is non-zero, as can have been arranged using the linker’s undocumented /win32version switch, then the kernel overrides the true Windows version numbers that would otherwise be set into these PEB members:
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x014C | 0x0230 |
VOID (*PostProcessInitRoutine) (VOID); |
5.0 and higher |
0x0150 | 0x0238 |
PVOID TlsExpansionBitmap; |
5.0 and higher |
0x0154 | 0x0240 |
ULONG TlsExpansionBitmapBits [0x20]; |
5.0 and higher |
0x01D4 | 0x02C0 |
ULONG SessionId; |
5.0 and higher |
0x02C4 |
UCHAR Padding5 [4]; |
6.3 and higher |
The SessionId is one of the two PEB members that Microsoft documented when required to disclose use of internal APIs by so-called middleware.
Insertion of the next three members for Windows XP produces the last known case of members whose offset varies between versions. Don’t miss the irony that this was done in the name of application compatibility.
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x01D8 | 0x02C8 |
ULARGE_INTEGER AppCompatFlags; |
5.1 and higher |
0x01E0 | 0x02D0 |
ULARGE_INTEGER AppCompatFlagsUser; |
5.1 and higher |
0x01E8 | 0x02D8 |
PVOID pShimData; |
5.1 and higher |
0x01D8 (5.0); 0x01EC |
0x02E0 |
PVOID AppCompatInfo; |
5.0 and higher |
0x01DC (5.0); 0x01F0 |
0x02E8 |
UNICODE_STRING CSDVersion; |
5.0 and higher |
The AppCompatFlags and AppCompatFlagsUser members are set by APPHELP.DLL from TAG_FLAG_MASK_KERNEL (0x5005) and TAG_FLAG_MASK_USER (0x5008) tags for the process’s description in an SDB file. In the XML that SDB files are compiled from, the two are evaluated from the MASK attribute in a <FLAG> tag whose TYPE attribute is KERNEL or USER, respectively.
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x01F8 | 0x02F8 |
ACTIVATION_CONTEXT_DATA const *ActivationContextData; |
5.1 and higher |
0x01FC | 0x0300 |
ASSEMBLY_STORAGE_MAP *ProcessAssemblyStorageMap; |
5.1 and higher |
0x0200 | 0x0308 |
ACTIVATION_CONTEXT_DATA const *SystemDefaultActivationContextData; |
5.1 and higher |
0x0204 | 0x0310 |
ASSEMBLY_STORAGE_MAP *SystemAssemblyStorageMap; |
5.1 and higher |
0x0208 | 0x0318 |
ULONG_PTR MinimumStackCommit; |
5.1 and higher |
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x020C | 0x0320 |
FLS_CALLBACK_INFO *FlsCallback; |
5.2 to 1809 |
PVOID SparePointers [4]; |
1903 and higher | ||
0x0210 (5.2 to 1809) | 0x0328 |
LIST_ENTRY FlsListHead; |
5.2 to 1809 |
0x0218 (5.2 to 1809) | 0x0338 |
PVOID FlsBitmap; |
5.2 to 1809 |
0x021C | 0x0340 |
ULONG FlsBitmapBits [4]; |
5.2 to 1809 |
ULONG SpareUlongs [5]; |
1903 and higher | ||
0x022C (5.2 to 1809) | 0x0350 |
ULONG FlsHighIndex; |
5.2 to 1809 |
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x0230 | 0x0358 |
PVOID WerRegistrationData; |
6.0 and higher |
0x0234 | 0x0360 |
PVOID WerShipAssertPtr; |
6.0 and higher |
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x0238 | 0x0368 |
PVOID pContextData; |
6.1 only |
PVOID pUnused; |
6.2 and higher | ||
0x023C | 0x0370 |
PVOID pImageHeaderHash; |
6.1 and higher |
0x0240 | 0x0378 |
union { ULONG TracingFlags; struct { /* bit fields, follow link */ }; }; |
6.1 and higher |
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x0248 | 0x0380 |
ULONGLONG CsrServerReadOnlySharedMemoryBase; |
6.2 and higher |
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x0250 | 0x0388 |
ULONG TppWorkerpListLock; |
1511 and higher |
0x0254 | 0x0390 |
LIST_ENTRY TppWorkerpList; |
1511 and higher |
0x025C | 0x03A0 |
PVOID WaitOnAddressHashTable [0x80]; |
1511 and higher |
0x045C | 0x07A0 |
PVOID TelemetryCoverageHeader; |
1709 and higher |
0x0460 | 0x07A8 |
ULONG CloudFileFlags; |
1709 and higher |
0x0464 | 0x07AC |
ULONG CloudFileDiagFlags; |
1803 and higher |
0x0468 | 0x07B0 |
CHAR PlaceholderCompatibiltyMode; |
1803 and higher |
0x0469 | 0x07B1 |
CHAR PlaceholderCompatibilityModeReserved [7]; |
1803 and higher |
0x0470 | 0x07B8 |
LEAP_SECOND_DATA *LeapSecondData; |
1809 and higher |
0x0474 | 0x07C0 |
union { ULONG LeapSecondFlags; struct { ULONG SixtySecondEnabled : 1; ULONG Reserved : 31; }; }; |
1809 and higher |
0x0478 | 0x07C4 |
ULONG NtGlobalFlag2; |
1809 and higher |
The NtGlobalFlag2 member is indeed named for being in some sense an extension of the much older NtGlobalFlag. Each corresponds to a registry value that can be in either or both of two well-known keys. Each also is the name of a variable in the kernel (one exported, the other only internal), which the kernel initialises from the corresponding registry value in the Session Manager key. This then provides the initial value for the corresponding PEB member, which may then be re-initialised from the same-named registry value in the program’s subkey of the Image File Execution Options.
Only one flag in the new set of them is yet known to be defined. A set 0x00000001 bit in the data for the GlobalFlag2 registry value becomes a set 0x00000001 bit in the NtGlobalFlag2 member. From there it may set the SixtySecondEnabled bit in union with the LeapSecondFlags. The intended effect is that the newly exported RtlpTimeFieldsToTime and RtlpTimeToTimeFields functions become leap-second-aware: when LeapSecondData is available, these functions accommodate 60 as the seconds field in a time.
This support for leap seconds was all new for the 1809 release and thus was also still new, roughly, for the article Leap Seconds for the IT Pro: What you need to know at a Microsoft blog dated Feb 14 2019. Years later, on 27th January 2023, this is still the only match that Google finds when asked to search microsoft.com for pages that contain GlobalFlag2. This is a good example of a trend in what passes as documentation. At various levels of Windows administration and programming, it is often that Microsoft’s only disclosure of some new feature, large or small, is a blog. Administrators and programmers are inevitably grateful that Microsoft employees take the time to blog. But let’s please not overlook that these blogs are not documentation. The helpfulness of Microsoft’s employees in explaining new features in fast-moving development, and the readiness of occasionally desperate administrators and programmers to latch on to this help, disguises that Microsoft is systematically skipping the work of documenting these features.