<index> / <windows-internals> / peb
[ en | fr ]
┌───────────────────────┐
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
└───────────────────────┘
Process Environment Block (PEB)
~ lululufr
SOMMAIRE
  0  qu'est-ce que le peb
  1  structure du peb
  2  champs clés
  3  le ldr — liste des modules chargés
  4  surface d'attaque

──[ 0. Qu'est-ce que le PEB ]──

Le Process Environment Block (PEB) est une structure user-mode que Windows 
maintient pour chaque processus en cours. Elle contient la config et l'état d'un
processus tels que vus depuis Ring 3. Un PEB par processus. Les threads ont une structure analogue, le TEB (Thread
Environment Block), avec les infos propres au thread et un pointeur vers le PEB
du processus.
    Adresse : le PEB vit en mémoire user-mode, atteignable depuis Ring 3.
    Son adresse est stockée dans le TEB, accessible via les registres
    de segment FS (x86) ou GS (x64).

──[ 1. Structure du PEB ]──

Le layout du PEB documenté par Microsoft est volontairement vague — beaucoup de 
champs sont marqués "Reserved". Les chercheurs ont reversé la structure complète
au fil des années.
    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. Champs clés ]──

BeingDebugged (offset 0x02)
    Un bool sur un BYTE. Le système le met à 1 quand un débogueur est
    attaché. Pas mal de malwares vérifient ça au démarrage pour détecter
    un environnement d'analyse et changer de comportement.

ProcessParameters
    Pointe vers RTL_USER_PROCESS_PARAMETERS — chemin de l'image, ligne
    de commande, répertoire de travail, titre de fenêtre, variables
    d'environnement.

Ldr
    Pointeur vers PEB_LDR_DATA — sans doute le champ le plus important
    si tu écris du shellcode ou si tu l'analyses.

──[ 3. Le Ldr — Liste des modules chargés ]──

PEB.Ldr pointe vers un PEB_LDR_DATA qui contient trois listes doublement 
chaînées de toutes les DLL chargées dans le processus :
    InLoadOrderModuleList        — ordre de chargement des modules
    InMemoryOrderModuleList      — ordre par adresse virtuelle en mémoire
    InInitializationOrderModuleList — ordre d'initialisation des DLL

Chaque nœud est un LDR_DATA_TABLE_ENTRY avec l'adresse de base de la DLL, sa 
taille, son nom, etc.
graph TD
    P[PEB] --> L[PEB_LDR_DATA]
    L --> A[InLoadOrderModuleList]
    L --> B[InMemoryOrderModuleList]
    L --> C[InInitializationOrderModuleList]
    A --> E1[l'exe lui-même]
    A --> E2[ntdll.dll]
    A --> E3[kernel32.dll]
    A --> E4[...]
Pour un processus Windows standard, l'ordre de chargement est prévisible :
    1. L'exécutable lui-même
    2. ntdll.dll
    3. kernel32.dll
    4. Imports supplémentaires

C'est cette prévisibilité que le shellcode position-indépendant exploite : on 
parcourt InMemoryOrderModuleList pour trouver kernel32.dll à un offset connu, on
parse sa table d'exports, on résout les adresses des fonctions à l'exécution.
──[ 4. Surface d'attaque ]──

Le PEB a une grosse surface d'attaque. Techniques principales qui s'appuient 
dessus : Bypass d'anti-debug
    Lire BeingDebugged à l'offset 0x02, le patcher à 0. Suffit pour
    flouer IsDebuggerPresent() et compagnie sans toucher à aucune API.

Résolution de DLL en shellcode
    Le shellcode ne peut pas hardcoder d'adresses (ASLR). Il parcourt
    les listes Ldr du PEB pour localiser kernel32.dll, puis parse la
    table d'exports PE pour trouver GetProcAddress — après quoi il peut
    résoudre n'importe quoi.

Process Hollowing
    En injectant dans un processus suspendu, l'attaquant doit souvent
    réparer des champs du PEB (ImageBaseAddress, point d'entrée) pour
    que le processus reprenne sur le code injecté plutôt que sur l'image
    légitime.

Spoofing de ligne de commande (PEB patching)
    La ligne de commande vit dans ProcessParameters (mémoire user-mode).
    On peut spawn un enfant en suspendu, patcher la ligne de commande
    dans son PEB avec un truc inoffensif, puis le reprendre — le callback
    kernel se déclenche avec la fausse ligne de commande pendant que la
    vraie tourne.
    // Localiser l'adresse du PEB du processus enfant
    NtQueryInformationProcess(hChild, ProcessBasicInformation, &pbi, ...);
    // Écraser CommandLine dans ProcessParameters
    WriteProcessMemory(hChild, cmdlineAddr, realCmd, realCmdLen, NULL);
    // Reprendre l'enfant
    ResumeThread(pi.hThread);