Objectif du cours
Ce chapitre présente les outils essentiels du reverse engineering, mais surtout comment les utiliser ensemble.
L'idée importante est :
Code: Select all
un seul outil ne suffit presque jamais
Code: Select all
Ghidra → comprendre la structure du programme
x64dbg → observer l'exécution réelle
Process Monitor → voir fichiers / registre / processus
Process Explorer → modules / handles / threads
WinDbg → user-mode avancé et kernel
DUMPBIN / PE viewer → structure du PE
Le but est de savoir :
Code: Select all
quel outil utiliser
pour quel problème
et dans quel ordre
Il existe deux grandes manières d'analyser un programme.
Analyse statique
On analyse le binaire sans l'exécuter.
Outils typiques :
Code: Select all
Ghidra
IDA
DUMPBIN
PE viewers
strings
- les fonctions ;
- les imports ;
- les strings ;
- les xrefs ;
- les graphes ;
- les sections ;
- les structures du PE.
On exécute le programme et on observe son comportement réel.
Outils :
Code: Select all
x64dbg
WinDbg
Process Monitor
Process Explorer
- les registres ;
- la mémoire ;
- les appels API ;
- les fichiers ouverts ;
- les accès registre ;
- les threads ;
- les modules chargés.
En pratique :
Code: Select all
statique = où regarder
dynamique = confirmer ce qui se passe réellement
2. Post-mortem et live analysis
Post-mortem
On analyse après l'exécution :
- dump mémoire ;
- crash dump ;
- fichier PE ;
- logs ;
- traces.
On observe le programme pendant qu'il tourne.
Exemple :
Code: Select all
breakpoint sur CreateFileW
↓
programme continue
↓
breakpoint déclenché
↓
inspection RCX
↓
chemin du fichier trouvé
3. Désassembleurs
Un désassembleur transforme le code machine en assembleur.
Exemple machine :
Code: Select all
48 8B 41 08
Code: Select all
mov rax, [rcx+8]
- où commence le code ;
- où finissent les données ;
- quelles adresses sont des fonctions ;
- quels sauts sont liés ;
- quelles zones sont réellement exécutables.
Important
Un désassembleur ne "comprend" pas vraiment le programme comme un humain.
Il reconstruit une représentation.
4. Basic Blocks
Un basic block est un bloc d'instructions exécuté sans branchement interne.
Exemple :
Code: Select all
mov eax, [rcx]
cmp eax, 10
jle failed
Les graphes de contrôle affichent ces blocs sous forme de boîtes reliées.
C'est extrêmement utile pour comprendre :
- if ;
- else ;
- boucles ;
- switch ;
- chemins de retour.
Ghidra est particulièrement utile pour l'analyse statique.
Quand tu importes un PE :
- Ghidra analyse les sections ;
- détecte les fonctions ;
- résout les imports ;
- trouve les strings ;
- construit les xrefs ;
- génère du pseudo-code.
Code: Select all
Listing
Decompiler
Functions
Symbol Tree
Strings
Defined Data
References
Function Graph
Une fonction détectée peut apparaître comme :
Code: Select all
FUN_1400012A0
Mais après analyse, tu peux la renommer :
Code: Select all
FUN_1400012A0
↓
ReadConfig
Code: Select all
CheckSession
Plus tu renommes :
- fonctions ;
- variables ;
- paramètres ;
- structures ;
7. Strings dans Ghidra
Les strings sont souvent l'un des meilleurs points d'entrée.
Exemple :
Code: Select all
"Access denied"
"C:\\config.ini"
"Global\\MyMutex"
"Invalid password"
Code: Select all
string
↓
Show References To
↓
fonction qui utilise la string
↓
analyse de la fonction
8. Xrefs
Xref signifie :
Code: Select all
cross-reference
Code: Select all
qui utilise cette fonction ?
qui utilise cette variable ?
qui référence cette chaîne ?
Code: Select all
VirtualAlloc
↓
Xrefs
↓
3 fonctions l'appellent
Réflexe
Quand tu trouves quelque chose d'intéressant :
Code: Select all
fonction
string
global
import
9. Function Graph
Le graph view permet de visualiser les basic blocks.
Exemple logique :
Code: Select all
cmp eax, 10
|
+---+---+
| |
>10 <=10
| |
success failed
Code: Select all
if / else
10. Call Graph
Un call graph montre les relations entre fonctions.
Exemple :
Code: Select all
main
├── Init
├── LoadConfig
├── Connect
│ └── ResolveHost
└── Cleanup
Pour un gros binaire, c'est souvent plus utile que de lire toutes les instructions.
11. Décompilateurs
Un décompilateur tente de reconstruire du pseudo-code C/C++.
Exemple assembleur :
Code: Select all
mov eax, [rcx]
test eax, eax
je failed
add eax, 5
ret
Code: Select all
if (*param_1 == 0)
return 0;
return *param_1 + 5;
Code: Select all
pseudo-code != code source original
Le compilateur a pu :
- optimiser ;
- inliner ;
- supprimer des variables ;
- recalculer des constantes ;
- réorganiser du code ;
- fusionner des branches.
Code: Select all
int x = 5;
return x * 2;
Code: Select all
mov eax, 10
ret
Réflexe
Utilise le pseudo-code pour comprendre rapidement.
Vérifie l'assembleur si quelque chose paraît incohérent.
13. Reverse .NET
Pour .NET, le code est souvent beaucoup plus facile à reconstruire.
On analyse :
Code: Select all
CIL / MSIL
metadata
classes
methods
types
Aujourd'hui, on peut utiliser différents outils modernes.
Le point important :
Code: Select all
reverse .NET != reverse natif C/C++
En natif, beaucoup d'informations disparaissent.
14. Debuggers
Un debugger permet de contrôler l'exécution.
Il peut :
- arrêter le programme ;
- continuer ;
- exécuter instruction par instruction ;
- afficher les registres ;
- afficher la stack ;
- inspecter la mémoire ;
- poser des breakpoints ;
- modifier certaines valeurs.
Code: Select all
x64dbg
Run
Continue jusqu'au prochain breakpoint ou événement.
Pause
Interrompt le programme.
Step Into
Exécute une instruction.
Si c'est un `call`, entre dans la fonction.
Step Over
Exécute le `call` sans entrer dedans.
Très utile pour éviter :
Code: Select all
std::cout
ucrtbase
KernelBase
ntdll
Step Out
Continue jusqu'au retour de la fonction courante.
16. Breakpoints logiciels
Un breakpoint logiciel modifie temporairement le code pour provoquer une interruption.
Typiquement avec une instruction :
Code: Select all
INT3
C'est le breakpoint classique.
Exemple :
Code: Select all
bp VirtualAlloc
17. Hardware Breakpoints
Les breakpoints matériels utilisent les registres de debug du CPU.
Ils sont utiles notamment pour surveiller :
- exécution ;
- lecture mémoire ;
- écriture mémoire ;
- accès mémoire.
Tu as trouvé une variable en mémoire :
Code: Select all
000001F4A1002000
Code: Select all
qui modifie cette valeur ?
Quand la valeur est modifiée :
Code: Select all
break
↓
RIP montre l'instruction responsable
18. Breakpoint sur API
C'est probablement l'une des techniques les plus importantes pour ton reverse Windows.
Exemple :
Code: Select all
VirtualAlloc
Quand il se déclenche :
Code: Select all
RCX = lpAddress
RDX = size
R8 = flags
R9 = protection
Code: Select all
RAX = adresse allouée
19. Registres importants sous Windows x64
Les registres les plus utiles :
Code: Select all
RAX → retour / calculs
RCX → argument 1
RDX → argument 2
R8 → argument 3
R9 → argument 4
RSP → stack pointer
RBP → base éventuelle de frame
RIP → instruction courante
Code: Select all
RBX
RSI
RDI
R12-R15
20. Flags
Les flags déterminent souvent les sauts conditionnels.
Exemple :
Code: Select all
cmp eax, 10
je equal
`je` examine notamment le Zero Flag.
Pour le reverse, retiens :
Code: Select all
cmp / test
↓
flags
↓
jcc
La stack contient beaucoup d'informations utiles.
Tu peux y retrouver :
- adresses de retour ;
- arguments supplémentaires ;
- variables locales ;
- pointeurs ;
- chaînes temporaires.
Code: Select all
RCX
RDX
R8
R9
Exemple :
Code: Select all
CreateFileW
Il faut donc examiner :
Code: Select all
RSP + offsets
22. Dump mémoire
Le dump permet d'afficher la mémoire brute.
Tu peux suivre :
Code: Select all
RCX
RAX
RSP
adresse
pointeur
- octets ;
- ASCII ;
- Unicode ;
- pointeurs ;
- structures.
Code: Select all
RCX = 000001F4A1003000
Tu vois :
Code: Select all
43 00 3A 00 5C 00 ...
Code: Select all
C:\...
23. Suivre un pointeur
Si :
Code: Select all
RAX = 000001F4A1000000
Code: Select all
[RAX] = 000001F4A2000000
Tu dois être à l'aise avec :
Code: Select all
registre
↓
adresse
↓
contenu
↓
nouvelle adresse éventuelle
La Memory Map de x64dbg est très utile.
Elle montre :
- modules ;
- stack ;
- heap ;
- private memory ;
- mapped memory ;
- protections.
Code: Select all
Base Size Protection
000001F4A1000000 1000 RW
25. Modules
Un processus charge plusieurs modules.
Exemple :
Code: Select all
program.exe
ntdll.dll
kernel32.dll
KernelBase.dll
ucrtbase.dll
- retrouver leur base ;
- calculer des RVA ;
- identifier les DLL ;
- retrouver une fonction.
Le debugger montre les threads du processus.
Pour chaque thread, tu peux regarder :
- RIP ;
- stack ;
- état ;
- adresse de démarrage.
27. x64dbg : workflow minimal
Pour un petit programme inconnu :
1
Ouvre le programme.
2
Regarde :
Code: Select all
imports
strings
modules
memory map
Choisis une API intéressante.
4
Pose un breakpoint.
5
Lance le programme.
6
Quand ça casse :
Code: Select all
RCX
RDX
R8
R9
RSP
Suis les pointeurs.
8
Remonte vers l'appelant.
C'est souvent suffisant pour comprendre beaucoup de programmes.
28. Exercice x64dbg : VirtualAlloc
Programme :
Code: Select all
void* p = VirtualAlloc(
nullptr,
0x2000,
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE
);
Code: Select all
breakpoint VirtualAlloc
Code: Select all
RCX = 0
RDX = 2000h
R8 = 3000h
R9 = 4
Code: Select all
VirtualAlloc(NULL, 0x2000, 0x3000, 0x4)
Après retour :
Code: Select all
RAX = nouvelle adresse
29. Exercice x64dbg : retrouver une chaîne
Supposons :
Code: Select all
lea rcx, [rip+1234h]
call SomeFunction
Code: Select all
RIP + 1234h
Tu trouves :
Code: Select all
"config.ini"
Code: Select all
xrefs / références
30. Exercice x64dbg : reconstruire un if
Tu vois :
Code: Select all
cmp eax, 5
jne failed
mov ebx, 1
jmp end
failed:
xor ebx, ebx
end:
Code: Select all
if (eax == 5)
ebx = 1;
else
ebx = 0;
La structure logique l'est.
31. Exercice x64dbg : reconstruire une boucle
Tu vois :
Code: Select all
xor ecx, ecx
loop:
cmp ecx, 10
jge done
inc ecx
jmp loop
done:
Code: Select all
for (int i = 0; i < 10; i++)
32. Ghidra + x64dbg ensemble
Le workflow le plus puissant pour toi :
Code: Select all
Ghidra
↓
trouver fonction intéressante
↓
noter RVA
↓
x64dbg
↓
module base + RVA
↓
breakpoint
↓
observer runtime
Dans Ghidra :
Code: Select all
fonction RVA = 0x2450
Code: Select all
base = 0x00007FF700000000
Code: Select all
0x00007FF700000000 + 0x2450
=
0x00007FF700002450
33. Attention à l'ASLR
Avec ASLR, la base du module peut changer.
Donc :
Code: Select all
adresse absolue Ghidra
Le bon réflexe est :
Code: Select all
RVA = adresse - image base
Code: Select all
runtime = base actuelle + RVA
WinDbg peut aussi debugger un processus user-mode.
Il est très utile pour :
- symboles ;
- exceptions ;
- stacks ;
- modules ;
- structures Windows.
Pour du debug système avancé, WinDbg devient très puissant.
35. Debuggers kernel-mode
Un debugger kernel permet d'observer l'ensemble du système.
Il peut voir :
- drivers ;
- threads kernel ;
- processus ;
- objets ;
- mémoire kernel ;
- structures internes.
Un mauvais état peut planter toute la machine cible.
36. WinDbg kernel
Le scénario recommandé :
Code: Select all
PC hôte
↓
WinDbg
↓
connexion debug
↓
VM Windows cible
- arrêtée ;
- debuggée ;
- snapshotée ;
- restaurée.
37. Pourquoi utiliser une VM
Avantages :
- isolation ;
- snapshots ;
- récupération rapide ;
- crashes sans casser la machine principale ;
- tests kernel plus sûrs.
Code: Select all
WinDbg + VM
38. System-Monitoring Tools
Le reverse ne se limite pas à l'assembleur.
On peut observer le comportement du programme depuis l'extérieur.
Exemples :
- fichiers ouverts ;
- clés registre ;
- processus créés ;
- DLL chargées ;
- connexions réseau ;
- handles.
Code: Select all
system monitoring
Process Monitor est l'un des outils les plus utiles.
Il permet de voir :
- File System ;
- Registry ;
- Process / Thread ;
- Image Load.
Tu lances un programme.
ProcMon montre :
Code: Select all
CreateFile
C:\ProgramData\App\config.dat
SUCCESS
Ensuite :
Code: Select all
x64dbg
↓
breakpoint CreateFileW
↓
retrouver l'appel exact
Sans filtres, ProcMon affiche énormément d'événements.
Exemple de filtre :
Code: Select all
Process Name is target.exe
Code: Select all
Operation is CreateFile
Code: Select all
Path contains config
Code: Select all
réduire le bruit
Process Explorer permet d'inspecter un processus.
Tu peux voir :
- arbre de processus ;
- PID ;
- threads ;
- DLL ;
- handles ;
- propriétés ;
- consommation mémoire.
Exemple :
Code: Select all
target.exe
↓
DLLs
↓
mylibrary.dll
Tu peux ensuite l'analyser dans Ghidra.
42. Handles dans Process Explorer
Tu peux retrouver :
Code: Select all
File
Mutex
Section
Event
Key
Code: Select all
Mutant
\BaseNamedObjects\MyMutex
Tu peux chercher la chaîne dans Ghidra.
43. TCPView
TCPView permet de voir :
- connexions TCP ;
- UDP ;
- ports locaux ;
- ports distants ;
- processus associés.
Code: Select all
target.exe
TCP
192.168.1.5:52000
93.184.216.34:443
Ensuite tu peux poser des breakpoints sur :
Code: Select all
connect
send
recv
WSASend
WSARecv
Question :
Code: Select all
je veux trouver une fonction
Code: Select all
Ghidra
Code: Select all
je veux savoir quelles valeurs passent dans une API
Code: Select all
x64dbg
Code: Select all
je veux savoir quel fichier est ouvert
Code: Select all
Process Monitor
Code: Select all
je veux voir les modules et handles
Code: Select all
Process Explorer
Code: Select all
je veux voir le kernel
Code: Select all
WinDbg
Code: Select all
je veux inspecter les headers PE
Code: Select all
DUMPBIN / PE viewer
Patcher signifie modifier le programme.
Exemple simple :
Code: Select all
jne failed
Code: Select all
je failed
Code: Select all
nop
nop
Attention
Toujours conserver l'original.
46. Patching temporaire et permanent
Temporaire
Modification en mémoire pendant le debug.
Après fermeture du processus :
Code: Select all
patch perdu
Modification enregistrée dans le fichier PE.
Le programme reste modifié au prochain lancement.
47. Exemple simple de patch
Code :
Code: Select all
cmp eax, 1
jne failed
Tu peux patcher temporairement :
Code: Select all
jne failed
Code: Select all
nop
nop
Si le comportement change comme prévu, ton hypothèse était correcte.
48. Hex Editors
Un éditeur hexadécimal permet de voir les octets du fichier brut.
Exemple :
Code: Select all
75 05
Code: Select all
jne +5
Code: Select all
adresse runtime != offset fichier
49. Executable Dumping
Dumper un exécutable consiste à récupérer une image ou des informations sur le PE.
Cela peut servir à examiner :
- headers ;
- sections ;
- imports ;
- exports ;
- image mémoire.
50. DUMPBIN
DUMPBIN est fourni avec les outils Visual Studio.
Commandes utiles :
Code: Select all
dumpbin /headers program.exe
Code: Select all
dumpbin /imports program.exe
Code: Select all
dumpbin /exports library.dll
Code: Select all
dumpbin /disasm program.exe
51. PE Viewers
Un PE viewer permet d'examiner visuellement :
- DOS header ;
- NT headers ;
- sections ;
- imports ;
- exports ;
- resources ;
- directories.
Aujourd'hui, tu peux utiliser un viewer PE moderne.
Le principe reste exactement le même.
52. Dump mémoire d'une région
Supposons qu'après `VirtualAlloc`, tu vois :
Code: Select all
RAX = 000001F4A1000000
Tu peux :
Code: Select all
suivre RAX
↓
observer la région
↓
sauvegarder / comparer le contenu
53. Workflow pratique complet
Pour un binaire inconnu :
Étape 1 — Process Monitor
Regarde ce que le programme fait :
Code: Select all
fichiers
registre
processus
Regarde :
Code: Select all
DLL
handles
threads
Cherche :
Code: Select all
imports
strings
xrefs
fonctions
Pose des breakpoints ciblés.
Regarde :
Code: Select all
RCX
RDX
R8
R9
RAX
stack
Reconstruis du pseudo-code.
Étape 6
Si nécessaire :
Code: Select all
patch
ou
WinDbg kernel
Tu vois :
Code: Select all
xor ecx, ecx
mov edx, 1000h
mov r8d, 3000h
mov r9d, 4
call VirtualAlloc
mov rbx, rax
Que dois-tu faire ?
Réponse :
Code: Select all
1. reconnaître VirtualAlloc
2. décoder les arguments
3. Step Over
4. récupérer RAX
5. suivre RAX dans le dump
6. regarder qui écrit dans cette zone
Process Monitor montre :
Code: Select all
target.exe
CreateFile
C:\temp\data.bin
SUCCESS
Code: Select all
ProcMon
↓
information comportementale
↓
x64dbg
↓
breakpoint CreateFileW
↓
regarder RCX
↓
retrouver appelant
Code: Select all
ReadFile
WriteFile
56. Mini défi : fonction sans symbole
Dans Ghidra :
Code: Select all
FUN_140002340
Code: Select all
VirtualAlloc
memcpy
VirtualProtect
Workflow :
Code: Select all
1. noter RVA
2. calculer adresse runtime
3. breakpoint x64dbg
4. observer arguments
5. suivre buffers
6. renommer fonction
Code: Select all
PrepareBuffer
57. Mini défi : hardware breakpoint
Tu trouves :
Code: Select all
player.hp = 100
Mais tu ne sais pas quelle fonction le modifie.
Tu poses :
Code: Select all
hardware breakpoint
on write
Plus tard :
Code: Select all
break
Code: Select all
sub dword ptr [rcx], eax
58. Mini défi : patch
Tu vois :
Code: Select all
cmp eax, 0
je failed
Patch temporaire :
Code: Select all
je failed
Code: Select all
nop
nop
Si la branche failed n'est plus prise et que le comportement change, tu as confirmé la logique.
59. Erreurs classiques
Erreur 1
Vouloir lire tout le programme depuis l'entry point.
Mieux :
Code: Select all
imports / strings / xrefs / API
Entrer dans toutes les fonctions système.
Tu peux perdre énormément de temps.
Utilise souvent :
Code: Select all
Step Over
Faire confiance aveuglément au décompilateur.
Toujours vérifier l'assembleur si nécessaire.
Erreur 4
Oublier l'ASLR.
Travaille avec les RVA.
Erreur 5
Confondre adresse, RVA et offset fichier.
Ce sont trois notions différentes.
Erreur 6
Utiliser un seul outil.
Le reverse devient beaucoup plus facile en combinant les outils.
60. Méthode à retenir
Ta méthode pratique peut être :
Code: Select all
1. comportement externe
→ Process Monitor / Process Explorer
2. structure
→ Ghidra
3. point intéressant
→ string / import / xref
4. confirmation runtime
→ x64dbg
5. arguments
→ RCX RDX R8 R9 + stack
6. valeurs de retour
→ RAX
7. données
→ dump / memory map
8. origine
→ remonter vers l'appelant
9. comprendre
→ pseudo-code
10. si besoin
→ patch / WinDbg
Les outils ont chacun un rôle.
Code: Select all
Ghidra
= structure du programme
Code: Select all
x64dbg
= comportement réel instruction par instruction
Code: Select all
Process Monitor
= fichiers / registre / événements système
Code: Select all
Process Explorer
= processus / modules / handles / threads
Code: Select all
WinDbg
= debugging avancé Windows et kernel
Code: Select all
DUMPBIN / PE viewer
= structure du PE
Code: Select all
ne choisis pas un outil parce que tu l'aimes
choisis-le parce qu'il répond à la question que tu te poses
Un bon reverse engineer ne passe pas son temps à lire des milliers d'instructions.
Il cherche des points d'entrée pertinents.
Exemple :
Code: Select all
"quel fichier est utilisé ?"
→ Process Monitor
"quelle fonction utilise cette string ?"
→ Ghidra
"quels arguments passent dans VirtualAlloc ?"
→ x64dbg
"quel objet est ouvert ?"
→ Process Explorer
"que fait ce driver ?"
→ WinDbg + VM
Le workflow moderne recommandé est donc :
Code: Select all
Process Monitor / Process Explorer
↓
Ghidra
↓
x64dbg
↓
WinDbg si nécessaire
Code: Select all
lire de l'assembleur
Code: Select all
collecter des indices
les relier
tester des hypothèses
et reconstruire le fonctionnement réel du programme
