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.
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);