CURRENT WORK ITEM - PREVIEW ONLY

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. If a process has no user-mode footprint, it has no PEB. If only in principle, if anything about a process can be properly managed in user mode without needing a transition to kernel mode, it goes in the PEB.

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, is loaded with a selector for a segment whose base address is that of 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 programming, Microsoft has all along had a macro or inline function, apparently named NtCurrentPeb, which reads directly from fs or gs, e.g.,

PEB *NtCurrentPeb (VOID)
{
    return (PEB *) __readfsdword (FIELD_OFFSET (TEB, ProcessEnvironmentBlock));
}

The difference scarcely matters at run time but has forensic significance because use of the latter in a high-level module, e.g., for an Internet Explorer DLL, shows knowledge not just of the PEB but of the TEB too and suggests that the programmers 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 ProcessBasicInformation case of the NtQueryInformationProcess function. This is exported by NTDLL in all known versions. It 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 process’s own threads—and writing to the queried process’s PEB certainly requires such synchronisation. In consequence, accessing another process’s PEB is beyond many programers who attempt it, e.g., for malware and for supposedly helpful system tools.

Documentation Status

In an ideal world, the PEB might be opaque outside the kernel and NTDLL. 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, 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—at least to know about when debugging—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.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

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

Offset (x86) Offset (x64) Definition Versions
0x00 0x00
BOOLEAN InheritedAddressSpace;
3.51 and higher
0x01 0x01
BOOLEAN ReadImageFileExecOptions;
3.51 and higher
0x02 0x02
BOOLEAN BeingDebugged;
3.51 and higher

Of the original members, the first to get documented was BeingDebugged. The ancient (documented) KERNEL32 function IsDebuggerPresent has always done nothing more than read this member from the current PEB.

Offset (x86) Offset (x64) Definition Versions
0x03 0x03
UCHAR SpareBool;
3.51 to early 5.2
union {
    UCHAR BitField;
    struct {
        /*  bit fields, see below  */
    };
};
late 5.2 and higher

A boolean in space caused by an alignment requirement might have been left undefined but was instead explicitly labelled as spare (if not from the start, then at least by the versions for which the symbol files have type information). It 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 at the end of the article, 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
0x04 0x08
PVOID Mutant;
3.51 and higher
0x08 0x10
PVOID ImageBaseAddress;
3.51 and higher
0x0C 0x18
PEB_LDR_DATA *Ldr;
3.51 and higher
0x10 0x20
RTL_USER_PROCESS_PARAMETERS *ProcessParameters;
3.51 and higher
0x14 0x28
PVOID SubSystemData;
3.51 and higher
0x18 0x30
HANDLE ProcessHeap;
3.51 and higher

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 WINTERN.H for all the world to know about. The ProcessHeap can’t be far behind, however: the ancient (documented) KERNEL32 function GetProcessHeap has always done nothing more than read the ProcessHeap from the current PEB, but very many Microsoft programs and DLLs instead read ProcessHeap by themselves.

Offset (x86) Offset (x64) Definition Versions
0x1C 0x38
PVOID FastPebLock;
3.51 to 5.0
RTL_CRITICAL_SECTION *FastPebLock;
5.1 and higher
0x20 0x40
PVOID FastPebLockRoutine;
3.51 to 5.1
PVOID SparePtr1;
early 5.2 only
PVOID AtlThunkSListPtr;
late 5.2 and higher
0x24 0x48
PVOID FastPebUnlockRoutine;
3.51 to 5.1
PVOID SparePtr2;
5.2 only
PVOID IFEOKey;
6.0 and higher

In early versions, NTDLL supports its exported 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 the lock. The lock does happen to be a critical section and the routines are just the expected RtlEnterCriticalSection and RtlLeaveCriticalSection. Still, not until version 5.1 is the lock’s nature formalised in the PEB and not until version 5.2 does NTDLL fix on the routines so that their addresses aren’t saved in the PEB

You might wonder why they ever were saved in the PEB. After all, the exported functions ought to suffice for user-mode code that’s outside NTDLL and wants to synchronise its access to the PEB. What fascinates me, and prompts this lengthy digression, is that only use I know of FastPebLock from outside NTDLL is in kernel mode. Moreover, it also uses the long-gone FastPebLockRoutine and FastPebUnlockRoutine members. Up to and including Windows XP, the kernel calls through these pointers—yes, such that the kernel executes NTDLL code at its user-mode address but in ring 0—to synchronise its proposed access to the PEB with access by other threads (most likely in user mode). Its only known use of this trick is when the exported (and documented) function RtlQueryRegistryValues expands environment variables whose names are found between percent signs in registry data that has the REG_EXPAND_SZ type.

Offset (x86) Offset (x64) Definition Versions
0x28 0x50
ULONG EnvironmentUpdateCount;
3.51 to 5.2
union {
    ULONG CrossProcessFlags;
    struct {
        /*  bit fields, see below  */
    };
};
6.0 and higher
0x2C 0x58
PVOID KernelCallbackTable;
3.51 to 5.2
union {
    PVOID KernelCallbackTable;
    PVOID UserSharedInfoPtr;
};
6.0 and higher

Yes, there is a plan to write something here.

Offset (x86) Offset (x64) Definition Versions
0x30 0x60
ULONG SystemReserved [2];
3.51 to 5.0
ULONG SystemReserved [1];
5.1 and higher
0x34 0x64
struct {
    ULONG ExecuteOptions : 2;
    ULONG SpareBits : 30;
};
early 5.1 and early 5.2
ULONG SpareUlong;
late 5.2 to 6.0
ULONG AtlThunkSListPtr32;
late 5.1, and 6.1 and higher

Yes, there is a plan to write something here.

Offset (x86) Offset (x64) Definition Versions
0x38 0x68
PEB_FREE_BLOCK *FreeList;
3.51 to early 6.0
ULONG SparePebPtr0;
late 6.0 only
PVOID ApiSetMap;
6.1 and higher

Yes, there is a plan to write something here.

Offset (x86) Offset (x64) Definition Versions
0x3C 0x70
ULONG TlsExpansionCounter;
3.51 and higher
0x40 0x78
PVOID TlsBitmap;
3.51 and higher
0x44 0x80
ULONG TlsBitmapBits [2];
3.51 and higher
0x4C 0x88
PVOID ReadOnlySharedMemoryBase;
3.51 and higher
0x50 0x90
PVOID ReadOnlySharedMemoryHeap;
3.51 to 5.2
PVOID HotpatchInformation;
6.0 and higher
0x54 0x98
PVOID *ReadOnlyStaticServerData;
3.51 and higher
0x58 0xA0
PVOID AnsiCodePageData;
3.51 and higher
0x5C 0xA8
PVOID OemCodePageData;
3.51 and higher
0x60 0xB0
PVOID UnicodeCaseTableData;
3.51 and higher
0x64 0xB8
ULONG NumberOfProcessors;
3.51 and higher
0x68 0xBC
ULONG NtGlobalFlag;
3.51 and higher
0x70 0xC0
LARGE_INTEGER CriticalSectionTimeout;
3.51 and higher
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
0x94 0xF8
PVOID GdiSharedHandleTable;
3.51 and higher

Appended for Windows NT 4.0

Offset (x86) Offset (x64) Definition Versions
0x98 0x0100
PVOID ProcessStarterHelper;
4.0 and higher
0x9C 0x0108
ULONG GdiDCAttributeList;
4.0 and higher
0xA0 0x0110
PVOID LoaderLock;
4.0 only
RTL_CRITICAL_SECTION *LoaderLock;
5.0 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
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];
4.0 and higher (x64)
0x014C 0x0230
VOID (*PostProcessInitRoutine) (VOID);
4.0 and higher

Appended for Windows 2000

Offset (x86) Offset (x64) Definition Versions
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

The SessionId was one of the two PEB members that Microsoft documented when required to disclose use of internal APIs by so-called middleware.

Insertion of three members for Windows XP produces the first known cases of members whose offset varies between versions:

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

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 MinimumStackCommit;
5.1 and higher

Appended for Windows Server 2003

Offset (x86) Offset (x64) Definition Versions
0x020C 0x0320
FLS_CALLBACK_INFO *FlsCallback;
5.2 and higher
0x0210 0x0328
LIST_ENTRY FlsListHead;
5.2 and higher
0x0218 0x0338
PVOID FlsBitmap;
5.2 and higher
0x021C 0x0340
ULONG FlsBitmapBits [4];
5.2 and higher
0x022C 0x0350
ULONG FlsHighIndex;
5.2 and higher

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, see below  */
    };
};
6.1 and higher

Appended for Windows 8

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

Bit Fields

The build of version 5.2 for Windows Server 2003 SP1 defined bit fields in what had been a SpareBool at offset 0x03. A single byte, named BitField, overlays the bits.

Mask Definition Versions
0x01
UCHAR ImageUsedLargePages : 1;
late 5.2 and higher
0x02
UCHAR IsProtectedProcess : 1;
6.0 and higher
0x04 (6.0 to 6.2)
UCHAR IsLegacyProcess : 1;
6.0 to 6.2
0x08 (6.0 to 6.2);
0x04
UCHAR IsImageDynamicallyRelocated : 1;
6.0 and higher
0x10 (late 6.0 to 6.2);
0x08
UCHAR SkipPatchingUser32Forwarders : 1;
late 6.0 and higher
0x20 (6.2);
0x10
UCHAR IsPackagedProcess : 1;
6.2 and higher
0x40 (6.2);
0x20
UCHAR IsAppContainer: 1;
6.2 and higher
0x40
UCHAR IsProtectedProcessLight : 1;
6.3 and higher
 
UCHAR SpareBits : 7;
late 5.2 only
UCHAR SpareBits : 4;
early 6.0 only
UCHAR SpareBits : 3;
late 6.0 to 6.1
UCHAR SpareBits : 1;
6.2 and higher

Cross-Process Flags

Mask Definition Versions
0x00000001
ULONG ProcessInJob : 1;
6.0 and higher
0x00000002
ULONG ProcessInitializing : 1;
6.0 and higher
0x00000004
ULONG ProcessUsingVEH : 1;
6.1 and higher
0x00000008
ULONG ProcessUsingVCH : 1;
6.1 and higher
0x00000010
ULONG ProcessUsingFTH : 1;
6.1 and higher
 
ULONG ReservedBits0 : 30;
6.0 only
ULONG ReservedBits0 : 27;
6.1 and higher

Tracing Flags

Mask Definition Versions
0x00000001
ULONG HeapTracingEnabled : 1;
6.1 and higher
0x00000002
ULONG CritSecTracingEnabled : 1;
6.1 and higher
0x00000004
ULONG LibLoaderTracingEnabled : 1;
6.2 and higher
 
ULONG SpareTracingBits : 30;
6.1 only
ULONG SpareTracingBits : 29;
6.2 and higher