Process Environment Block (PEB) is a user mode data structure which applies over a whole process. It is designed to be used by the application-mode code in the operating system libraries, such as NTDLL.dll, Kernel32.dll. Through the use of PEB one can obtain the list of loaded modules, process startup arguments, ImageBaseAddress, heap address, check whether program is being debugged or not, find base addresses of any imported DLLs and a lot others. Malicious codes perform PEB enumeration and further walk through the export table of any module to get the function addresses.


To study more about PEB, I will use WinDbg and attach to any process (eg. notepad.exe). I am running WinDbg(64bit) on Windows 8. Finally, we will write an assembly code which does the same job without the use of debugger.


For attaching to any process in Windbg, we need to load the symbols first. You need a good internet connection if referring the symbols directly from the Microsoft website, else you have to download it into your local hard drive.





Address of PEB and address of ImageBaseAddress can be found using a single WinDbg command:


0:001> !peb  

PEB at 000007f65cd26000

InheritedAddressSpace:    No

ReadImageFileExecOptions: No

BeingDebugged:            Yes

ImageBaseAddress:         000007f65db90000

Ldr                       000007fc6cd288c0

Ldr.Initialized:          Yes

Ldr.InInitializationOrderModuleList: 0000006b53a32200 . 0000006b53a39fa0

Ldr.InLoadOrderModuleList:           0000006b53a32360 . 0000006b53a39f80

Ldr.InMemoryOrderModuleList:         0000006b53a32370 . 0000006b53a39f90



In 64bit OS, PEB lies at offset 60h from Thread Execution Block. This can be confirmed here:


0:001> dt nt!_TEB  


+0x000 NtTib            : _NT_TIB

+0x038 EnvironmentPointer : Ptr64 Void

+0x040 ClientId         : _CLIENT_ID

+0x050 ActiveRpcHandle  : Ptr64 Void

+0x058 ThreadLocalStoragePointer : Ptr64 Void

+0x060 ProcessEnvironmentBlock : Ptr64 _PEB

+0x068 LastErrorValue   : Uint4B

+0x06c CountOfOwnedCriticalSections : Uint4B



PEB can also be accessed using FS register. FS register can also help us find lots of information like pointer to current exception handler(FS:[0h]), process ID(FS:[20h]), environment pointer etc.


Now let’s have a look at the PEB structure.

0:001> dt nt!_PEB  


+0x000 InheritedAddressSpace : UChar

+0x001 ReadImageFileExecOptions : UChar

+0x002 BeingDebugged    : UChar

+0x003 BitField         : UChar

+0x003 ImageUsesLargePages : Pos 0, 1 Bit

+0x003 IsProtectedProcess : Pos 1, 1 Bit

+0x003 IsLegacyProcess  : Pos 2, 1 Bit

+0x003 IsImageDynamicallyRelocated : Pos 3, 1 Bit

+0x003 SkipPatchingUser32Forwarders : Pos 4, 1 Bit

+0x003 IsPackagedProcess : Pos 5, 1 Bit

+0x003 IsAppContainer   : Pos 6, 1 Bit

+0x003 SpareBits        : Pos 7, 1 Bit

+0x008 Mutant           : Ptr64 Void

+0x010 ImageBaseAddress : Ptr64 Void

+0x018 Ldr              : Ptr64 _PEB_LDR_DATA

+0x020 ProcessParameters : Ptr64 _RTL_USER_PROCESS_PARAMETERS

+0x028 SubSystemData    : Ptr64 Void

+0x030 ProcessHeap      : Ptr64 Void

+0x038 FastPebLock      : Ptr64 _RTL_CRITICAL_SECTION

+0x040 AtlThunkSListPtr : Ptr64 Void

+0x048 IFEOKey          : Ptr64 Void

+0x050 CrossProcessFlags : Uint4B

+0x050 ProcessInJob     : Pos 0, 1 Bit

+0x050 ProcessInitializing : Pos 1, 1 Bit

+0x050 ProcessUsingVEH  : Pos 2, 1 Bit

+0x050 ProcessUsingVCH  : Pos 3, 1 Bit

+0x050 ProcessUsingFTH  : Pos 4, 1 Bit

+0x050 ReservedBits0    : Pos 5, 27 Bits

+0x058 KernelCallbackTable : Ptr64 Void

+0x058 UserSharedInfoPtr : Ptr64 Void

+0x060 SystemReserved   : [1] Uint4B

+0x064 AtlThunkSListPtr32 : Uint4B

+0x068 ApiSetMap        : Ptr64 Void

+0x070 TlsExpansionCounter : Uint4B

+0x078 TlsBitmap        : Ptr64 Void

+0x080 TlsBitmapBits    : [2] Uint4B

+0x088 ReadOnlySharedMemoryBase : Ptr64 Void

+0x090 HotpatchInformation : Ptr64 Void

+0x098 ReadOnlyStaticServerData : Ptr64 Ptr64 Void

+0x0a0 AnsiCodePageData : Ptr64 Void

+0x0a8 OemCodePageData  : Ptr64 Void

+0x0b0 UnicodeCaseTableData : Ptr64 Void

+0x0b8 NumberOfProcessors : Uint4B

+0x0bc NtGlobalFlag     : Uint4B

+0x0c0 CriticalSectionTimeout : _LARGE_INTEGER

+0x0c8 HeapSegmentReserve : Uint8B

+0x0d0 HeapSegmentCommit : Uint8B

+0x0d8 HeapDeCommitTotalFreeThreshold : Uint8B

+0x0e0 HeapDeCommitFreeBlockThreshold : Uint8B

+0x0e8 NumberOfHeaps    : Uint4B

+0x0ec MaximumNumberOfHeaps : Uint4B

+0x0f0 ProcessHeaps     : Ptr64 Ptr64 Void

+0x0f8 GdiSharedHandleTable : Ptr64 Void

+0x100 ProcessStarterHelper : Ptr64 Void

+0x108 GdiDCAttributeList : Uint4B

+0x110 LoaderLock       : Ptr64 _RTL_CRITICAL_SECTION

+0x118 OSMajorVersion   : Uint4B

+0x11c OSMinorVersion   : Uint4B

+0x120 OSBuildNumber    : Uint2B

+0x122 OSCSDVersion     : Uint2B

+0x124 OSPlatformId     : Uint4B

+0x128 ImageSubsystem   : Uint4B

+0x12c ImageSubsystemMajorVersion : Uint4B

+0x130 ImageSubsystemMinorVersion : Uint4B

+0x138 ActiveProcessAffinityMask : Uint8B

+0x140 GdiHandleBuffer  : [60] Uint4B

+0x230 PostProcessInitRoutine : Ptr64     void

+0x238 TlsExpansionBitmap : Ptr64 Void

+0x240 TlsExpansionBitmapBits : [32] Uint4B

+0x2c0 SessionId        : Uint4B

+0x2c8 AppCompatFlags   : _ULARGE_INTEGER

+0x2d0 AppCompatFlagsUser : _ULARGE_INTEGER

+0x2d8 pShimData        : Ptr64 Void

+0x2e0 AppCompatInfo    : Ptr64 Void

+0x2e8 CSDVersion       : _UNICODE_STRING

+0x2f8 ActivationContextData : Ptr64 _ACTIVATION_CONTEXT_DATA

+0x300 ProcessAssemblyStorageMap : Ptr64 _ASSEMBLY_STORAGE_MAP

+0x308 SystemDefaultActivationContextData : Ptr64 _ACTIVATION_CONTEXT_DATA

+0x310 SystemAssemblyStorageMap : Ptr64 _ASSEMBLY_STORAGE_MAP

+0x318 MinimumStackCommit : Uint8B

+0x320 FlsCallback      : Ptr64 _FLS_CALLBACK_INFO

+0x328 FlsListHead      : _LIST_ENTRY

+0x338 FlsBitmap        : Ptr64 Void

+0x340 FlsBitmapBits    : [4] Uint4B

+0x350 FlsHighIndex     : Uint4B

+0x358 WerRegistrationData : Ptr64 Void

+0x360 WerShipAssertPtr : Ptr64 Void

+0x368 pUnused          : Ptr64 Void

+0x370 pImageHeaderHash : Ptr64 Void

+0x378 TracingFlags     : Uint4B

+0x378 HeapTracingEnabled : Pos 0, 1 Bit

+0x378 CritSecTracingEnabled : Pos 1, 1 Bit

+0x378 LibLoaderTracingEnabled : Pos 2, 1 Bit

+0x378 SpareTracingBits : Pos 3, 29 Bits

+0x380 CsrServerReadOnlySharedMemoryBase : Uint8B



PEB stores the list of all modules loaded in the current process’s address space. We follow the Ldr pointer, which takes us to another structure, _PEB_LDR_DATA. As we can see, offset 18h points to _PEB_LDR_DATA structure.


Structure of _PEB_LDR_DATA:

0:001> dt nt!_PEB_LDR_DATA 000007fc6cd288c0  


+0x000 Length           : 0x58

+0x004 Initialized      : 0x1 ”

+0x008 SsHandle         : (null)

+0x010 InLoadOrderModuleList : _LIST_ENTRY [ 0x0000006b`53a32360 – 0x0000006b`53a39f80 ]

+0x020 InMemoryOrderModuleList : _LIST_ENTRY [ 0x0000006b`53a32370 – 0x0000006b`53a39f90 ]

+0x030 InInitializationOrderModuleList : _LIST_ENTRY [ 0x0000006b`53a32200 – 0x0000006b`53a39fa0 ]

+0x040 EntryInProgress  : (null)

+0x048 ShutdownInProgress : 0 ”

+0x050 ShutdownThreadId : (null)


The 3 doubly linked lists mentioned above store the list of DLLs in different orders. Here, the list of modules are stored in the order in which they were loaded, in the order they are located in memory and in the order in which they are initialized.

Let’s expand the InInitializationOrderModuleList field of _PEB_LDR_DATA structure as follows:

0:001> dt nt!_PEB_LDR_DATA 000007fc6cd288c0 InInitializationOrderModuleList.Flink /r1 


+0x030 InInitializationOrderModuleList       :  [ 0x0000006b`53a32200 – 0x0000006b`53a39fa0 ]

+0x000 Flink                                 : 0x0000006b`53a32200 _LIST_ENTRY [ 0x0000006b`53a32b90 – 0x000007fc`6cd288f0 ]


Flink stores the address of the next loader structure. On expanding the Flink of this List which gives us the first memory address as 0x0000006b`53a32b90.


Dumping the data stored in this memory, we get the following:

0:001> dd 0x0000006b`53a32200  

0000006b`53a32200  53a32b90 0000006b 6cd288f0 000007fc

0000006b`53a32210  6cbf0000 000007fc 00000000 00000000

0000006b`53a32220  001c0000 00000000 003c003a 00000000

0000006b`53a32230  53a320e0 0000006b 00140012 00000000

0000006b`53a32240  6cc5e048 000007fc 000002c4 0000ffff

0000006b`53a32250  6cd2e8f0 000007fc 6cd2e8f0 000007fc

0000006b`53a32260  51637f77 00000000 00000000 00000000

0000006b`53a32270  00000000 00000000 53a32300 0000006b



The memory address contains addresses of various modules loaded in the current process. So let’s check what addresses are being pointed by the 1st Ldr structure pointed by Flink.


0:001> dd 0000006b53a32b90  


0000006b`53a32b90  53a327e0 0000006b 53a32200 0000006b

0000006b`53a32ba0  69e50000 000007fc 69e522f0 000007fc

0000006b`53a32bb0  000f3000 00000000 00460044 00000000

0000006b`53a32bc0  53a32b20 0000006b 001e001c 0000006b

0000006b`53a32bd0  53a32b48 0000006b 000802cc 0000ffff

0000006b`53a32be0  6cd2e7f0 000007fc 6cd2e7f0 000007fc

0000006b`53a32bf0  50988aa6 00000000 00000000 00000000

0000006b`53a32c00  00000000 00000000 53a32c90 0000006b


The 3rd offset address 69e50000 000007fc(in Little Endian Format) should be the base address of Kernelbase.dll. Let’s check where in memory the Kernelbase.dll lies using “lm” command.


0:001> lm 


start             end                 module name

000007f6`5db90000 000007f6`5dbd0000   notepad    (deferred)

000007fc`658c0000 000007fc`6593d000   WINSPOOL   (deferred)

000007fc`65950000 000007fc`65bba000   COMCTL32   (deferred)

000007fc`67c80000 000007fc`67ca1000   dwmapi     (deferred)

000007fc`68890000 000007fc`68976000   uxtheme    (deferred)

000007fc`68c60000 000007fc`68cf6000   SHCORE     (deferred)

000007fc`69880000 000007fc`698dc000   bcryptPrimitives   (deferred)

000007fc`698e0000 000007fc`698ea000   CRYPTBASE   (deferred)

000007fc`69e50000 000007fc`69f43000   KERNELBASE   (deferred)

000007fc`69fe0000 000007fc`6b2c0000   SHELL32    (deferred)

000007fc`6b2c0000 000007fc`6b3f6000   KERNEL32   (deferred)


Kernelbase.dll is important because it performs many functions that were earlier performed by kernel32.dll and advapi32.dll in Windows Vista and lower OS.


In assembly language, the code goes this way:


  1. mov eax,  fs:[30h]            ; peb address
  2. mov eax, [eax+18h]        ; PEB->Ldr
  3. mov ebx, [eax+30h]       ; PEB-> InInitializationOrderModuleList.Flink
  4. mov ebx, [ebx]                 ; 1st module entry
  5. mov ebx, [ebx+20h]       ; kernelbase.dll



Step by step explanation of the program:

  1. PEB address 000007f65cd26000h is stored in eax.
  2. Store the _PEB_LDR_DATA structure address 000007fc6cd288c0 (i.e., value strored at location 000007f65cd26000h + 18h) in eax.
  3. Store PEB-> InInitializationOrderModuleList in ebx.
  4. Store the next Loader structure at address 0x0000006b`53a32b90 pointed by PEB-> InInitializationOrderModuleList.Flink in ebx.
  5. Finally we move the data stored on offset 0x20 (i.e., 000007fc, 69e50000) into eax register.



1] PEB structure

2] New Low-Level Binaries

3] Deep Drive into OS Internals using Windbg