Evolution of Process Environment Block (PEB)

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 PDBs (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):

PEB Evolution

PEB Evolution PDF

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)

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.

21 Comments

    1. It works for me. If it returns ‘403 Forbidden’ then try multiple times, some times there are some problems with my hosting.

      Reply

  1. how can i know the base address of PEB in virtual memory. i tried to read that structure but i dont know exact location of PEB.

    Reply

    1. To get 32-bit PEB from 32-bit process:

      	BYTE* _teb = (BYTE*)__readfsdword(0x18);
      	PEB32* _peb = *(PEB32**)(_teb + 0x30);

      To get 64-bit PEB from 64-bit process:

      	BYTE* _teb = (BYTE*)__readgsqword(0x30);
      	PEB64* _peb = *(PEB64**)(_teb + 0x60);

      To get 64-bit PEB from 32-bit WoW64 process:

      	BYTE* _teb = (BYTE*)__readfsdword(0x18) - 0x2000;
      	DWORD64 _peb = *(DWORD64*)(_teb + 0x60);

      There are some magic consts in above snippets, but at least You don’t need to define TEB32/TEB64 structures to use them. Third snippet may not work on Windows 8+, I can’t test it on this platform at the moment.

      Reply

  2. i am interesting the method how to collect different ntdll.pdb/wntdll.pdb files from various versions of Windows, i want to get other windows dll’s pdb for various versions of Windows, can you tell me ? thank u!

    Reply

    1. Unfortunately it is not straightforward task :/ I was planning to describe my methodology, but it never happened. Maybe if I find some free afternoon I’ll finally write it down and publish on this blog, as it might be useful for some other people as well.

      Reply

  3. Hi, how did you obtain all those versions of ntdll.dll. I was recently tasked with the same thing, but I wanted to obtain the syscall numbers; however, I was left with manually installing different versions of Windows to do it though. I was wondering if there’s a nicer way around that to obtain the ntdll.dll’s?

    Reply

    1. Funny that this question pops up again in a very short time period. As I said in the comment above, it’s not straightforward procedure, but definitely it’s nicer and quicker than installing every windows in VM. I’ll try to push myself into writing blogpost that describes my methodology.

      Reply

      1. Hi, I’ve also downloaded the symbols from https://developer.microsoft.com/en-us/windows/hardware/download-symbols and obtained offsets that way; but I’ve noticed these aren’t complete at all; plus the hashes mismatched between those and the symbols take a lot of hard drive space, since I’ve had to install all of the PDBs (although I just need the ntdll.pdb). This approach was way faster than manually installing the Windows systems, but still not perfect – although there probably isn’t a perfect method to begin with. However, I’m interested in what you’ve done to obtain your DLLs/PDBs.

        Reply

  4. Hi, have you written the piece about obtaining all DLL/PDBs yet, I’m also interested in reading about it?

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *