PEB

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.

Access

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).

Other Processes

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.

Documentation Status

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.

Layout

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.

Original (More or Less)

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.

Appended for Windows NT 3.51

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

Appended for Windows NT 4.0

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:

Appended for Windows 2000

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.

Appended for Windows XP

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

Appended for Windows Server 2003

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

Appended for Windows Vista

Offset (x86) Offset (x64) Definition Versions
0x0230 0x0358
PVOID WerRegistrationData;
6.0 and higher
0x0234 0x0360
PVOID WerShipAssertPtr;
6.0 and higher

Appended for Windows 7

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

Appended for Windows 8

Offset (x86) Offset (x64) Definition Versions
0x0248 0x0380
ULONGLONG CsrServerReadOnlySharedMemoryBase;
6.2 and higher

Appended Later in Windows 10

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.