Digging into Windows PEB

Process Environment Block

Introduction

PEB has been present in Windows since the introduction of Win2k (Windows 2000). It has the highest-level knowledge of a process in kernel mode and the lowest-level in user mode

PEB is a structure available for every process at a fixed address in memory:

The PEB can be found at fs:[0x30] in the Thread Environment Block for x86 processes as well as at GS:[0x60] for x64 processes.

This structure contains useful information about the process such as the process name, process ID (PID), loaded modules (all PE files loaded in the memory including the program itself and its DLLs), and much more.

Before, continuing I would like to highlight in which step PEB is created on process creation.

Main Process Creation steps:

  1. Starting a program (calc.exe for example): calc.exe will call a win32 API function : CreateProcess which sends to the OS the request to create this process and start the execution.

  2. Creating the process data structures: Windows creates the process structure EPROCESS on kernel land for the newly created calc.exe process.

  3. Initialize the virtual memory: Then, Windows creates the process, virtual memory, and its representation of the physical memory and saves it inside the EPROCESS structure, creates the PEB structure with all necessary information, and then loads the main two DLLs that Windows applications will always need, which are ntdll.dll and kernel32.dll

  4. Loading the PE file.

  5. Start the execution.

  • PEB can be accessed from User Mode - Contains Process specific information

  • EPROCESS can be only be accessed from Kernel Mode

You need SeDebugPrivilege when overwriting the PEB of other processes that aren't spawned in your user context.

Debugging(Windbg)

Investigating Loaded DLLs

Using WinDbg (kernel debugging mode), let's first find the EPROCESS's address of our created process(t3nb3w.exe as an example) using the command:

!process 0 0

In the above screenshot, the value ffffd30ccae08080 is the address of EPROCESS structure relate to t3nb3w.exe process. To inspect the process structure the following command will do the job:

!process ffffd30ccae08080

let's dig into some highlighted elements of EPROCESS structure:

  • Cid: represent the process id in HEX format

  • ParentCid: represent the parent process id in HEX format.

  • Peb: The address of PEB.

You can visualize the same information via Process Hacker, except the Cid and ParentCid value will be in decimal format:

In a nutshell, the Executive Subsystem wants to track the t3nb3w.exe process so, it will associate a token with this process. This token represents the security context under which t3nb3w.exe and its threads are running.

Examining the PEB Structure

First, based on MSDN documentation the PEB structure is defined as below:

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  PVOID                         Reserved4[3];
  PVOID                         AtlThunkSListPtr;
  PVOID                         Reserved5;
  ULONG                         Reserved6;
  PVOID                         Reserved7;
  ULONG                         Reserved8;
  ULONG                         AtlThunkSListPtr32;
  PVOID                         Reserved9[45];
  BYTE                          Reserved10[96];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved11[128];
  PVOID                         Reserved12[1];
  ULONG                         SessionId;
} PEB, *PPEB;

As you can see, the PEB is a robust structure, its members are very valuable to the Windows OS. Specifically, the member we are most interested in is Ldr.

In WinDbg(user mode debugging) the PEB structure of a specified process can be dumped using the following command:

dt _PEB 00283000// Displaying information of PEB structure located at 00283000(PEB's virtual memory) 

From the results above we see that the LDR structure is at an offset of 0x0c of the PEB structures base address.

Now, let's look at our main member that is at an offset of 0x0c from the base of the PEB structure, which is the _PEB_LDR_DATA. Using WinDbg we can also examine the _PEB_LDR_DATA structure:

Based on the above screenshot the _PEB_LDR_DATA structure contain InMemoryOrderModuleList, Microsoft defines this member as:

The head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure.

Indeed, InMemoryOrderModuleList points to the head of a doubly-linked list of LIST_ENTRY items, it doesn’t immediately take you to the LDR_DATA_TABLE_ENTRY structure for that module and it's logic since InMemoryOrderModuleList is a doubly-linked list where each list item points to an LDR_DATA_TABLE_ENTRY structure. Let’s first examine a LIST_ENTRY as defined on MSDN:

typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;
   struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

Assuming an x86 environment, these values are pointers to the next LIST_ENTRY structures that exist on LDR_DATA_TABLE_ENTRY associate with the loaded modules, this makes the size of this structure 8 bytes.

Specifically, it’s taking us to LIST_ENTRY structure InMemoryOrderLinks of _LDR_DATA_TABLE_ENTRY:

Now it's time to inspect _LDR_DATA_TABLE_ENTRY structure of every loaded module:

1st Module:

2nd Module:

3rd Module:

You notice that I highlight the DllBase member of every loaded module which specifies where in memory the DLL is to be loaded.

Here is a sample of assembly code that gets the base address of kernel32.dll:

BITS 32

global _start

section .text

_start:
	xor eax, eax              ; Avoid Null Bytes
	mov eax, [fs:eax + 0x30]	; EAX = PEB
	mov eax, [eax + 0xc]			; EAX = PEB->Ldr
	mov esi, [eax + 0x14]			; ESI = PEB->Ldr.InMemoryOrderModuleList
	lodsd											; EAX = Second module (ntdll.dll)
	xchg eax, esi							; move to next element
	lodsd											; EAX = Third(kernel32)
	mov eax, [eax + 0x10]			; EAX = Base address
  • (Lines 8-9) First, we set eax register to zero to avoid NULL bytes and use it in the next instruction. The instructionmov eax, [fs:eax + 0x30]saves the address of the PEB structure in eax. Windows uses the fs segment register to store the address of the TEB structure, the address of the PEB structure is located at an offset of 0x30 bytes.

  • (Line 10-11)The second mov instruction saves the address of the ldr structure in eax. The ldr structure is located at an offset of 0xc bytes of the PEB structure then the third mov instruction save the address of the InInitializationOrderModuleList list in esi. This list contains a list of the loaded modules.

  • Line( 12 - 14) At this moment we are on “InMemoryOrderLinks” of the first module using its first element "flink”, which is a pointer to the next module. Then, we placed this pointer in the esi register. The “lodsd” take esi as an argument then will return the result in the eax register. This means that after the lodsd instruction we will have the second module, ntdll.dll, in the eax register. We place this pointer in the esi by exchanging the values of eax and esi and use again the lodsd instruction to reach the 3rd module: kernel32.dll.

  • Line(15) Finally, we have in the eax register, the pointer to “InMemoryOrderLinks” of kernel32.dll. Adding 0x10 bytes will get the base address of kernel32.dll.

At this point, you can identify the modules it needs to use and reference them directly. This method is very well-known in fileless malware as it can be used to dynamically discover loaded modules when an implant is injected in memory and executed via another process. For example, a common method is to use the third entry of the list (which includes the base address of kernel32.dll) and enumerate the export table of kernel32.dll to find LoadLibrary() and start loading arbitrary DLLs required for its operation.

To sum up, we learned that we can retrieve the kernel32.dll base address which is the main DLL for writing shellcoding because it has APIs such as LoadLibrary, which allows you to load other libraries, and GetProcAddress that allows you to retrieve the address of an exported function or variable from the specified dynamic-link library (DLL). To access any Win32 API function inside any DLL, the shellcode must get the address of the kernel32.dll in its memory and parse its export table.

In the next post, we will parse the exported table of kernel 32.dll to retrieve the addresses of Loadlibrary&GetProcAddress.

Final Note: I am not a Windows Internal expert I'm just a learner, If you think I said anything incorrect anywhere, feel free to reach out to me and correct me, I would highly appreciate that. And finally, thank you very much for taking your time to read this post.

References

Last updated