Update 2016.09.14: This post is a bit outdated, if you are interested in some more recent research in this topic check out Terminus Project

Over one year ago I’ve published unified definition of PEB for x86 and x64 Windows (PEB32 and PEB64 in one definition). It was based on PEB taken from Windows 7 NTDLL symbols, but I was pretty confident that it should work on other versions of Windows as well. Recently someone left a comment under mentioned post: “Good, but its only for Windows 7”. It made me curious if it is really ‘only for Win7’. I was expecting that there might be some small differences between some field names, or maybe some new fields added at the end, but the overall structure should be the same. I’ve no other choice but to check it myself. I’ve collected 108 different ntdll.pdb/wntdll.pdb files from various versions of Windows and dumped _PEB structure from them (Dia2Dump ftw!). Here are some statistics:

_PEB was defined in 80 different PDB s (53 x86 PEBs and 27 x64 PEBs)

There was 11 unique PEBs for x86 , and 8 unique PEBs for x64 (those numbers doesn’t sum up, as starting from Windows 2003 SP1 there is always match between x86 and x64 version)

The total number of collected different _PEB definitions is equal to 11

I’ve put all the collected informations into nice table (click the picture to open PDF, more recent visualisation of this data is available here: http://terminus.rewolf.pl):

Left column of the table represents x86 offset, right column is x64 offset, green fields are supposed to be compatible across all windows versions starting from XP without any SP and ending at Windows 8 RTM, red (pink?, rose?) fields should be used only after careful verification if they’re working on a target system. At the top of the table, there is row called NTDLL TimeStamp, it is not the timestamp from the PE header but from the Debug Directory (IMAGE_DIRECTORY_ENTRY_DEBUG, LordPE can parse this structure). I’m using this timestamp as an unique identifier for NTDLL version, this timestamp is also stored in PDB files.

Now I can answer initial question: “Is my previous PEB32/PEB64 definition wrong ?” Yes and No. Yes, because it contains various fields specific for Windows 7 thus it can be considered as wrong. No, because most of the fields are exactly the same across all Windows versions, especially those fields that are usually used in third party software. To satisfy everyone, I’ve prepared another version of PEB32/PEB64 definition:

#pragma pack(push) #pragma pack(1) template < class T > struct LIST_ENTRY_T { T Flink ; T Blink ; } ; template < class T > struct UNICODE_STRING_T { union { struct { WORD Length ; WORD MaximumLength ; } ; T dummy ; } ; T _Buffer ; } ; template < class T, class NGF, int A > struct _PEB_T { union { struct { BYTE InheritedAddressSpace ; BYTE ReadImageFileExecOptions ; BYTE BeingDebugged ; BYTE _SYSTEM_DEPENDENT_01 ; } ; T dummy01 ; } ; T Mutant ; T ImageBaseAddress ; T Ldr ; T ProcessParameters ; T SubSystemData ; T ProcessHeap ; T FastPebLock ; T _SYSTEM_DEPENDENT_02 ; T _SYSTEM_DEPENDENT_03 ; T _SYSTEM_DEPENDENT_04 ; union { T KernelCallbackTable ; T UserSharedInfoPtr ; } ; DWORD SystemReserved ; DWORD _SYSTEM_DEPENDENT_05 ; T _SYSTEM_DEPENDENT_06 ; T TlsExpansionCounter ; T TlsBitmap ; DWORD TlsBitmapBits [ 2 ] ; T ReadOnlySharedMemoryBase ; T _SYSTEM_DEPENDENT_07 ; T ReadOnlyStaticServerData ; T AnsiCodePageData ; T OemCodePageData ; T UnicodeCaseTableData ; DWORD NumberOfProcessors ; union { DWORD NtGlobalFlag ; NGF dummy02 ; } ; LARGE_INTEGER CriticalSectionTimeout ; T HeapSegmentReserve ; T HeapSegmentCommit ; T HeapDeCommitTotalFreeThreshold ; T HeapDeCommitFreeBlockThreshold ; DWORD NumberOfHeaps ; DWORD MaximumNumberOfHeaps ; T ProcessHeaps ; T GdiSharedHandleTable ; T ProcessStarterHelper ; T GdiDCAttributeList ; T LoaderLock ; DWORD OSMajorVersion ; DWORD OSMinorVersion ; WORD OSBuildNumber ; WORD OSCSDVersion ; DWORD OSPlatformId ; DWORD ImageSubsystem ; DWORD ImageSubsystemMajorVersion ; T ImageSubsystemMinorVersion ; union { T ImageProcessAffinityMask ; T ActiveProcessAffinityMask ; } ; T GdiHandleBuffer [ A ] ; T PostProcessInitRoutine ; T TlsExpansionBitmap ; DWORD TlsExpansionBitmapBits [ 32 ] ; T SessionId ; ULARGE_INTEGER AppCompatFlags ; ULARGE_INTEGER AppCompatFlagsUser ; T pShimData ; T AppCompatInfo ; UNICODE_STRING_T < T > CSDVersion ; T ActivationContextData ; T ProcessAssemblyStorageMap ; T SystemDefaultActivationContextData ; T SystemAssemblyStorageMap ; T MinimumStackCommit ; } ; typedef _PEB_T < DWORD, DWORD64, 34 > PEB32 ; typedef _PEB_T < DWORD64, DWORD, 30 > PEB64 ; #pragma pack(pop) #pragma pack(push) #pragma pack(1) template <class T> struct LIST_ENTRY_T { T Flink; T Blink; }; template <class T> struct UNICODE_STRING_T { union { struct { WORD Length; WORD MaximumLength; }; T dummy; }; T _Buffer; }; template <class T, class NGF, int A> struct _PEB_T { union { struct { BYTE InheritedAddressSpace; BYTE ReadImageFileExecOptions; BYTE BeingDebugged; BYTE _SYSTEM_DEPENDENT_01; }; T dummy01; }; T Mutant; T ImageBaseAddress; T Ldr; T ProcessParameters; T SubSystemData; T ProcessHeap; T FastPebLock; T _SYSTEM_DEPENDENT_02; T _SYSTEM_DEPENDENT_03; T _SYSTEM_DEPENDENT_04; union { T KernelCallbackTable; T UserSharedInfoPtr; }; DWORD SystemReserved; DWORD _SYSTEM_DEPENDENT_05; T _SYSTEM_DEPENDENT_06; T TlsExpansionCounter; T TlsBitmap; DWORD TlsBitmapBits[2]; T ReadOnlySharedMemoryBase; T _SYSTEM_DEPENDENT_07; T ReadOnlyStaticServerData; T AnsiCodePageData; T OemCodePageData; T UnicodeCaseTableData; DWORD NumberOfProcessors; union { DWORD NtGlobalFlag; NGF dummy02; }; LARGE_INTEGER CriticalSectionTimeout; T HeapSegmentReserve; T HeapSegmentCommit; T HeapDeCommitTotalFreeThreshold; T HeapDeCommitFreeBlockThreshold; DWORD NumberOfHeaps; DWORD MaximumNumberOfHeaps; T ProcessHeaps; T GdiSharedHandleTable; T ProcessStarterHelper; T GdiDCAttributeList; T LoaderLock; DWORD OSMajorVersion; DWORD OSMinorVersion; WORD OSBuildNumber; WORD OSCSDVersion; DWORD OSPlatformId; DWORD ImageSubsystem; DWORD ImageSubsystemMajorVersion; T ImageSubsystemMinorVersion; union { T ImageProcessAffinityMask; T ActiveProcessAffinityMask; }; T GdiHandleBuffer[A]; T PostProcessInitRoutine; T TlsExpansionBitmap; DWORD TlsExpansionBitmapBits[32]; T SessionId; ULARGE_INTEGER AppCompatFlags; ULARGE_INTEGER AppCompatFlagsUser; T pShimData; T AppCompatInfo; UNICODE_STRING_T<T> CSDVersion; T ActivationContextData; T ProcessAssemblyStorageMap; T SystemDefaultActivationContextData; T SystemAssemblyStorageMap; T MinimumStackCommit; }; typedef _PEB_T<DWORD, DWORD64, 34> PEB32; typedef _PEB_T<DWORD64, DWORD, 30> PEB64; #pragma pack(pop)

Above version is system independent as all fields that are changing across OS versions are marked as _SYSTEM_DEPENDENT_xx. I’ve also removed all fields from the end that were added after Widnows XP.