<index> / <windows-internals> / peb
[ en | fr ]
┌───────────────────────┐
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
└───────────────────────┘
Process Environment Block (PEB)
~ lululufr
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.
graph TD
    P[PEB] --> L[PEB_LDR_DATA]
    L --> A[InLoadOrderModuleList]
    L --> B[InMemoryOrderModuleList]
    L --> C[InInitializationOrderModuleList]
    A --> E1[exe itself]
    A --> E2[ntdll.dll]
    A --> E3[kernel32.dll]
    A --> E4[...]
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);