PEB_LDR_DATA

The PEB_LDR_DATA structure is the main record of what modules are loaded in a process. It is essentially the head of three double-linked lists of LDR_DATA_TABLE_ENTRY structures which each represent one loaded module.

Access

Each process has the one PEB_LDR_DATA. Its address is kept in the Ldr member of the process’s PEB.

In early versions, the PEB_LDR_DATA is in its own heap allocation that is obtained while NTDLL initialises the process. In version 5.2 and higher, however, the process’s one and only PEB_LDR_DATA is in the NTDLL data. It can be handy, when debugging, to know that the name of the internal variable is PebLdr.

Documentation Status

In an ideal world, the PEB_LDR_DATA might be opaque outside NTDLL. But various high-level modules supplied with Windows over the years have used at least one member of the PEB_LDR_DATA, which 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. Anyone competent who was looking at the time, e.g., because they were paid to as work for a Technical Committee that was to enforce the settlement that compelled the disclosure, might have pointed out the PEB_LDR_DATA as a candidate. Starting with the SDK for Windows 7, WINTERNL.H presents a modified PEB_LDR_DATA that has just the InMemoryOrderModuleList member, plus padding that gets this member to the same offset as in the true structure. It seems unlikely that Microsoft will change the PEB_LDR_DATA in any way that moves this member.

Layout

Indeed, the PEB_LDR_DATA is surprisingly stable across Windows versions. No members have yet been moved or even redefined. The structure has grown only by extension. The following table shows the changing sizes:

Version Size (x86) Size (x64)
3.51 to 5.0 0x24  
5.1 to 6.0 before Windows Vista SP1 0x28 0x48
6.0 from Windows Vista SP1, and higher 0x30 0x58

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. Symbol files for earlier versions do not contain type definitions for the PEB_LDR_DATA, but inspection confirms that all members that were in use by then were used the same way as far back as Windows NT 3.51.

Offset (x86) Offset (x64) Definition Versions
0x00 0x00
ULONG Length;
3.51 and higher
0x04 0x04
BOOLEAN Initialized;
3.51 and higher
0x08 0x08
PVOID SsHandle;
3.51 and higher
0x0C 0x10
LIST_ENTRY InLoadOrderModuleList;
3.51 and higher
0x14 0x20
LIST_ENTRY InMemoryOrderModuleList;
3.51 and higher
0x1C 0x30
LIST_ENTRY InInitializationOrderModuleList;
3.51 and higher
0x24 0x40
PVOID EntryInProgress;
5.1 and higher
0x28 0x48
BOOLEAN ShutdownInProgress;
6.0 from Windows Vista SP1, and higher
0x2C 0x50
HANDLE ShutdownThreadId;
6.0 from Windows Vista SP1, and higher

The Length and Initialized members are set to the size, in bytes, of the structure, and TRUE, respectively, when the structure is prepared. They are not known to change.

No use is known of the SsHandle member.

Though EntryInProgress is retained in the symbol files at least to Windows 10, no use of it is known in Windows 8 and higher. In earlier versions, what it points to, when it points to anything, is in fact a LDR_DATA_TABLE_ENTRY for a DLL whose imports are to be resolved. In version 5.1 and higher, imports are subject to activation contexts. The importing DLL may redirect its imports via a manifest. If a callback function for DLL manifest probing has been set by an earlier call to the undocumented NTDLL export LdrSetDllManifestProber, then EntryInProgress is set while the callback is made.

Though ShutdownThreadId is declared as a HANDLE, it is indeed the thread ID as suggested by its name. It is picked up from the UniqueThread member of the CLIENT_ID in the TEB of the thread that asks to terminate the process.