Understanding the Windows PEB
Introduction
During malware analysis, I often encounter shellcode that abuse the PEB structure to dinamically constructs it’s own import table.
The PEB (Process Environment Block), according to MSDN is a structure that contains process informations, but I prefer Geoff Chapell’s definition that describe the PEB structure like a process’s user-mode representation created by the kernel and managed mostly in user mode, and in practice is used to share data between processes instead of creating an inter-process-communication (IPC).
PEB has been present in Windows since the introduction of Win2k.
How to use
In all recent Win x86 versions, the FS register points to the TEB (Thread Environment Block) structure defined in Winternl.h like a structure that contains the running thread’s informations similarly like PEB, and also visible using the public avaiable microsoft debug symbols.
1 | 0:000> dt ntdll!_TEB |
For all recents x64 windows version, the GS register stores the TEB address, you may wondering if these segment registers have some processor-defined purpose like CS (Code Segment) DS (Data Segment) ES(Destination Segment), the answer is no, but instead are given purpose by the OS’s running, also their name was chosen to continue the alphabetic order😂.
For more informations about the background history behind this read this interesting SO thread.
Now if you looked at the TEB structure above you may noticed that at offset 0x30 there is a pointer to a _PEB structure, let’s take a look at this structure.
1 | 0:000> dt ntdll!_PEB |
You can see a lot of important informations about the process i.e. the BeingDebugged
field is used a lot from malware to avoid common dinamical analysis techniques that use the function IsDebuggerPresent()
to check if the program is debugged, this function reversed using ghidra generates the following pseudocode:
1 | ulonglong IsDebuggerPresent(void) |
The program that I wrote below, in an x86 environment, does the same thing without importing the Kernel32.dll function in order to hide from static analysis of the import table.
Becoming familiar with this structure is important because a lot of Windows functions just read the PEB and TEB informations, and you can use these structure to obfuscate code or to simply avoid additional dependencies.
Another interesting field is the Ldr
located at offset 0xC, this is a pointer to a _PEB_LDR_DATA
data structure that contains information about the loaded modules for the process, shellcode will typically walk this data structure to find the base address of loaded dlls.
1 | 0:000> dt ntdll!_PEB_LDR_DATA |
According to microsoft docs the field InMemoryOrderModuleList
represents the head of a doubly-linked list where each items in the list is a pointer to a LDR_DATA_TABLE_ENTRY
data structure.
1 | 0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY |
You may wondering why there are 3 double-linked lists, the response is that theoretically they should represent different things as their names suggest but in reality they are equals, these lists are defined following the MSDN.
1 | typedef struct _LIST_ENTRY { |
Assuming a 32bit environment we can see that these struct has an 8byte size, why we need to know this? Because if we choose to use the InMemoryOrderLinks
list when we follow this pointer it’s taking us to _LIST_ENTRY
structure InMemoryOrderLinks
of the _LDR_DATA_TABLE_ENTRY
of the next module that isn’t the base of the structure, for this reason we need to subtract 8 bytes from that address in order to point correctly at the _LDR_DATA_TABLE_ENTRY
struct.
Case Study
Scope
We are going to find the loaded modules of a simple program using winDbg to inspect them easily.
Hands On
Open winDbg and attach a debugger on some process, and execute the following command to obtain TEB
information, in my case:
1 | 0:000> dt ntdll!_TEB 0x003e0000 |
Now we can access and analyze the PEB
structure using the address stored in the PEB Address
field typing the following command:
1 | 0:000> dt ntdll!_PEB 0x003dd000 |
Now remember that ldr field stores the address of _PEB_LDR_DATA
data structure that contains informations about the loaded modules for this process.
1 | 0:000> dt ntdll!_PEB_LDR_DATA 0x77945d80 |
Now it’s equal to choose to follow the InLoadOrderModuleList
or InMemoryOrderModuleList
, in this case I choosed to follow InMemoryOrderModuleList
, it could be a good exercise shows that these lists are equivalents.
So it’s important now to remember what I said before, when we use the InMemoryOrderLinks
we need to subtract 8bytes if we are in a 32bit environment, because otherwise we encounter the following problem:
1 | 0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY 0x6344a0 |
You can see that the structure is incoerent, we can also deduce that FullDllName
contains the value of BaseDllName
so probably the structure was shifted of a number of bytes equals to 0x2c - 0x24 = 8, cool! We demonstrate that to allineate correctly the structure we need to subtract 8 bytes
1 | 0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY (0x6344a0 - 0x8) |
Now we can see that the structure seems correct, if we follow the linked list we would obtain the following modules, let’s try to extract the following modules!
1 | 0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY (0x634398 - 0x8) |
We can clearly see that the second loaded module is the ntdll.dll
, in fact this is the Windows core library and it should be loaded before any other dll libraries, except ntoskrnl.dll
.
1 | 0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY (0x634880 - 0x8) |
The third loaded module is Kernel32.dll
, keep in mind that the modules are loaded following this pattern:
Exe module
ntdll.dll
kernel32.dll
The rest of the modules depends on the program flow, dependencies, ecc.., it’s very important this concept because the majority of the shellcode that I analyze always retrieve the base address of the kernel32.dll module in order to call i.e. GetProcAddress
, LoadLibrary
.
Final Thoughts
Understand the way Processes and Threads are represented helps analyzing malicious code, but also writing efficient code from a programming perspective.
Understanding the Windows PEB
https://captwake.github.io/Windows-Internals/UnderstandingPEB/