CONTENTS
0 what is the peb
1 peb structure
2 key fields
3 the ldr — loaded module list
4 attack surface
──[ 0. What is the PEB ]──
The Process Environment Block (PEB) is a user-mode struct Windows keeps for
every running process. It holds the config and state of a process as seen from
Ring 3.
One PEB per process. Threads get an analogous struct called the TEB (Thread
Environment Block), which has per-thread info and a pointer back to the process
PEB.
Address: the PEB lives in user-mode memory, reachable from Ring 3.
Its address sits in the TEB, accessed via the FS (x86) or GS (x64)
segment register.
──[ 1. PEB Structure ]──
Microsoft's documented PEB layout is intentionally sparse — most fields are
marked "Reserved". Researchers reversed the full thing over the years.
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;
──[ 2. Key Fields ]──
BeingDebugged (offset 0x02)
A BYTE bool. System sets it to 1 when a debugger is attached. Lots of
malware checks this at startup to detect analysis and switch behaviour.
ProcessParameters
Points to RTL_USER_PROCESS_PARAMETERS — process image path, command
line, working directory, window title, env vars.
Ldr
Pointer to PEB_LDR_DATA — arguably the most important field if you
write shellcode or analyse it.
──[ 3. The Ldr — Loaded Module List ]──
PEB.Ldr points to a PEB_LDR_DATA that holds three doubly-linked lists of every
DLL currently loaded in the process:
InLoadOrderModuleList — order modules were loaded
InMemoryOrderModuleList — order by virtual address in memory
InInitializationOrderModuleList — order DLLs were initialised
Each node is an LDR_DATA_TABLE_ENTRY with the DLL's base address, size, name,
etc.
For a standard Windows process the load order is predictable:
1. The exe itself
2. ntdll.dll
3. kernel32.dll
4. Additional imports
This predictability is what position-independent shellcode exploits: walk
InMemoryOrderModuleList to find kernel32.dll at a known offset, parse its export
table, resolve function addresses at runtime.
──[ 4. Attack Surface ]──
The PEB has a wide attack surface. Key techniques that lean on it:
Anti-debug bypass
Read BeingDebugged at offset 0x02, patch it to 0. Defeats
IsDebuggerPresent() and similar without touching any API.
Shellcode DLL resolution
Shellcode can't hardcode addresses (ASLR). It walks the PEB Ldr lists
to locate kernel32.dll, then parses the PE export table to find
GetProcAddress — after which it can resolve anything else.
Process Hollowing
When injecting into a suspended process, the attacker often has to
fix up PEB fields (ImageBaseAddress, entry point) so the process
resumes on the injected code instead of the legitimate image.
Command-line spoofing (PEB patching)
The command line lives in ProcessParameters (user-mode memory). You
can spawn a child suspended, patch its PEB command line to something
benign, then resume — the kernel callback fires with the fake command
line while the real one runs.
// Locate PEB address of child process
NtQueryInformationProcess(hChild, ProcessBasicInformation, &pbi, ...);
// Overwrite CommandLine in ProcessParameters
WriteProcessMemory(hChild, cmdlineAddr, realCmd, realCmdLen, NULL);
// Resume the child
ResumeThread(pi.hThread);