Introduction au reverse engineering : fondations, usages, outils et cadre légal

Salut ce forum est dédié à comprendre les entrailles de l'ordinateur au plus bas niveau bienvenue dans le monde du reverse et de l'assembleur

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 114
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Introduction au reverse engineering : fondations, usages, outils et cadre légal

Post by Hydraxx »

Introduction au reverse engineering : fondations, usages, outils et cadre légal

Introduction

Le reverse engineering, ou rétro-ingénierie, consiste à étudier un système existant afin d'en comprendre le fonctionnement interne.

Dans le domaine logiciel, cela signifie généralement partir d'un programme compilé, d'un exécutable, d'une bibliothèque, d'un pilote ou d'un autre composant logiciel sans disposer nécessairement de son code source, puis tenter de reconstruire son comportement, son architecture et sa logique interne.

Le but n'est donc pas de créer un programme à partir d'une spécification, mais de faire le chemin inverse :
  • observer le programme ;
  • analyser son comportement ;
  • étudier son code machine ;
  • identifier ses structures internes ;
  • comprendre les choix réalisés par le développeur ;
  • reconstruire progressivement une représentation compréhensible du programme.
Le reverse engineering mélange plusieurs domaines :
  • assembleur ;
  • architecture processeur ;
  • systèmes d'exploitation ;
  • compilation ;
  • programmation bas niveau ;
  • analyse logique ;
  • débogage ;
  • analyse mémoire.
Il ne faut donc pas voir le reverse engineering comme une simple lecture d'assembleur. Lire les instructions est seulement une partie du travail.

1. Qu'est-ce que le reverse engineering ?

Définition générale

Le reverse engineering consiste à partir d'un produit fini afin d'en retrouver le fonctionnement.

Dans un programme classique, le développement suit approximativement ce chemin :

Code: Select all

idée
  ↓
algorithme
  ↓
code source
  ↓
compilation
  ↓
programme exécutable
Le reverse engineering effectue le chemin opposé :

Code: Select all

programme exécutable
  ↓
code machine
  ↓
assembleur
  ↓
logique du programme
  ↓
algorithmes
  ↓
comportement général
Le code source original n'est généralement pas récupérable à l'identique.

Le reverse engineer reconstruit plutôt une représentation équivalente permettant de comprendre ce que fait le programme.

Par exemple, un programme C++ peut contenir :

Code: Select all

if (password == 1234)
{
    AccessGranted();
}
Après compilation, on peut retrouver une logique similaire sous forme d'instructions assembleur :

Code: Select all

cmp eax, 1234
jne failed
call AccessGranted
L'objectif du reverse engineer est de reconnaître le comportement haut niveau caché derrière ces instructions.

2. Pourquoi faire du reverse engineering ?

Le reverse engineering peut avoir de nombreux objectifs légitimes.

Comprendre un logiciel

Il est parfois nécessaire d'étudier un logiciel lorsque :
  • le code source n'est plus disponible ;
  • le développeur original n'est plus présent ;
  • la documentation est insuffisante ;
  • le programme utilise un format inconnu ;
  • on souhaite comprendre son architecture.
Interopérabilité

Le reverse engineering peut être utilisé afin de permettre à deux logiciels ou systèmes différents de communiquer.

Un programme peut par exemple utiliser :
  • un format de fichier propriétaire ;
  • un protocole réseau non documenté ;
  • une structure de données interne ;
  • une API non documentée.
L'étude du comportement du programme permet alors de reproduire le format ou le protocole nécessaire à l'interopérabilité.

Sécurité informatique

Le reverse engineering est extrêmement important en sécurité informatique.

Il permet notamment :
  • d'analyser une vulnérabilité ;
  • de comprendre un crash ;
  • d'étudier un programme inconnu ;
  • de rechercher un comportement dangereux ;
  • de vérifier les protections d'un programme ;
  • d'analyser du code malveillant.
Analyse de logiciels malveillants

Les analystes de malware utilisent fréquemment le reverse engineering pour comprendre :
  • comment le programme démarre ;
  • quels fichiers il crée ;
  • quelles clés de registre il modifie ;
  • quelles fonctions Windows il utilise ;
  • comment il communique ;
  • comment il persiste sur le système ;
  • comment il cache son comportement.
Le reverse engineering permet donc de comprendre le fonctionnement d'un programme sans avoir besoin de son code source.

Audit de sécurité

Un binaire peut également être analysé afin d'identifier des erreurs de programmation ou des vulnérabilités.

Quelques exemples :
  • mauvaise validation de taille ;
  • buffer overflow ;
  • mauvaise gestion mémoire ;
  • droits excessifs ;
  • erreurs cryptographiques ;
  • logique d'authentification incorrecte.
Cryptographie

Le reverse engineering peut servir à examiner la manière dont une application utilise la cryptographie.

Il est important de distinguer deux choses :
  • le secret d'une clé cryptographique ;
  • le secret d'un algorithme.
Un bon système cryptographique ne doit normalement pas dépendre du fait que son algorithme reste secret.

En revanche, certaines applications utilisent des algorithmes propriétaires ou des implémentations particulières qu'un analyste peut chercher à comprendre.

DRM et protections logicielles

Les systèmes de Digital Rights Management cherchent à contrôler l'utilisation ou la copie de contenu numérique.

Le reverse engineering peut être utilisé pour comprendre le fonctionnement de ces mécanismes.

Cela ne signifie pas que leur contournement soit nécessairement légal.

Le cadre juridique dépend du pays, du type de logiciel, du contrat de licence et de l'objectif de l'analyse.

3. Reverse engineering logiciel

Le reverse engineering logiciel peut être séparé en plusieurs niveaux.

Reverse de haut niveau

À haut niveau, on cherche surtout à comprendre :
  • l'architecture générale ;
  • les composants ;
  • les interactions entre modules ;
  • les fichiers utilisés ;
  • les dépendances ;
  • le fonctionnement global.
On ne regarde pas nécessairement chaque instruction assembleur.

Des outils d'observation système peuvent déjà fournir beaucoup d'informations.

Reverse de bas niveau

À bas niveau, on étudie directement :
  • le code machine ;
  • l'assembleur ;
  • les registres ;
  • la pile ;
  • la mémoire ;
  • les appels de fonctions ;
  • les structures binaires.
C'est cette partie qui nécessite une bonne compréhension de l'architecture processeur et du système d'exploitation.

4. System-Level Reversing

Le system-level reversing consiste à observer le programme depuis l'extérieur.

On cherche à comprendre son comportement vis-à-vis du système d'exploitation.

On peut par exemple surveiller :
  • les fichiers ouverts ;
  • les fichiers créés ;
  • les clés de registre consultées ;
  • les processus créés ;
  • les threads ;
  • les DLL chargées ;
  • les connexions réseau ;
  • les handles ;
  • les accès mémoire.
Sous Windows, des outils comme Process Monitor ou Process Explorer sont particulièrement utiles.

Par exemple, avant même d'ouvrir un programme dans un désassembleur, on peut découvrir qu'il :

Code: Select all

ouvre config.dat
charge winhttp.dll
crée un thread
contacte un serveur
écrit une valeur dans le registre
Ces informations permettent déjà de construire une première représentation du comportement du programme.

5. Code-Level Reversing

Le code-level reversing consiste à analyser directement le code du programme.

On travaille alors principalement avec :
  • le code machine ;
  • l'assembleur ;
  • un désassembleur ;
  • un debugger ;
  • éventuellement un décompilateur.
Le but est de reconstruire la logique du programme.

Par exemple :

Code: Select all

mov eax, [rcx]
add eax, 5
cmp eax, 25
jne failed
Un reverse engineer pourra reconstruire une logique proche de :

Code: Select all

value = *ptr + 5;

if (value != 25)
{
    goto failed;
}
Cette traduction n'est pas forcément identique au code original, mais elle permet de comprendre le comportement.

6. Fondations nécessaires

Assembleur

L'assembleur est indispensable pour comprendre le code produit par le compilateur.

Les notions importantes comprennent :
  • registres ;
  • mov ;
  • lea ;
  • push / pop ;
  • cmp ;
  • test ;
  • jmp ;
  • jcc ;
  • call ;
  • ret ;
  • arithmétique ;
  • opérations binaires ;
  • accès mémoire.
En x86-64, il faut également comprendre des registres comme :

Code: Select all

RAX
RBX
RCX
RDX
RSI
RDI
RSP
RBP
R8
R9
R10
R11
R12
R13
R14
R15
Systèmes d'exploitation

Un programme ne fonctionne pas seul.

Il dépend du système d'exploitation.

Pour faire du reverse sous Windows, il est utile de comprendre :
  • processus ;
  • threads ;
  • mémoire virtuelle ;
  • DLL ;
  • handles ;
  • fichiers ;
  • registre ;
  • API Windows ;
  • exceptions ;
  • permissions ;
  • tokens ;
  • chargement des exécutables.
Plus on connaît Windows, plus il devient facile de comprendre ce que fait un programme Windows.

Compilateurs

Le code analysé a généralement été transformé par un compilateur.

Le reverse engineer doit donc comprendre que :

Code: Select all

code source != assembleur direct
Le compilateur peut :
  • réorganiser le code ;
  • supprimer des variables ;
  • utiliser des registres ;
  • fusionner des expressions ;
  • dérouler des boucles ;
  • inline des fonctions ;
  • supprimer du code inutile.
Deux programmes sources très similaires peuvent donc produire des assembleurs différents.

Optimisations

Un programme compilé en Debug est généralement plus facile à analyser.

Un programme compilé en Release peut contenir de nombreuses optimisations.

Exemple simple :

Code: Select all

int x = 5;
int y = x * 2;
return y;
Le compilateur peut directement produire quelque chose correspondant à :

Code: Select all

mov eax, 10
ret
La variable x et même l'opération de multiplication peuvent disparaître.

7. Machines virtuelles et bytecode

Tous les programmes ne sont pas directement compilés en code machine natif.

Certains langages produisent du bytecode exécuté par une machine virtuelle.

Exemples :
  • Java ;
  • .NET ;
  • certaines VM propriétaires.
Le reverse engineering de bytecode peut être différent du reverse natif.

Le bytecode conserve parfois davantage d'informations proches du code source.

Un programme natif C/C++ peut être plus difficile à reconstruire car beaucoup d'informations disparaissent lors de la compilation.

8. Le processus de reverse engineering

Il n'existe pas une seule méthode universelle.

Cependant, un processus classique ressemble à ceci.

Étape 1 : observation

Avant de lire le code, observer le programme.

Questions utiles :
  • que fait-il ?
  • quels fichiers utilise-t-il ?
  • quelles DLL charge-t-il ?
  • crée-t-il des processus ?
  • utilise-t-il le réseau ?
  • demande-t-il des privilèges ?
Étape 2 : analyse statique

L'analyse statique consiste à analyser le programme sans l'exécuter.

On peut examiner :
  • les chaînes de caractères ;
  • les imports ;
  • les sections ;
  • les fonctions ;
  • les références croisées ;
  • le code désassemblé.
Étape 3 : analyse dynamique

L'analyse dynamique consiste à observer le programme pendant son exécution.

On peut :
  • placer des breakpoints ;
  • exécuter instruction par instruction ;
  • observer les registres ;
  • observer la pile ;
  • inspecter la mémoire ;
  • suivre les appels de fonctions.
Étape 4 : reconstruire la logique

On transforme progressivement l'assembleur en logique haut niveau.

Par exemple :

Code: Select all

cmp ecx, 0
je empty
mov eax, 1
ret
empty:
xor eax, eax
ret
peut être interprété comme :

Code: Select all

if (value == 0)
    return 0;
else
    return 1;
Étape 5 : validation

Une hypothèse doit être testée.

On peut par exemple :
  • placer un breakpoint ;
  • modifier une entrée ;
  • observer une valeur ;
  • comparer plusieurs exécutions ;
  • suivre une fonction.
Le reverse engineering est donc un travail itératif.

9. Les outils du reverse engineer

Outils de monitoring système

Ces outils observent le comportement global du programme.

Ils permettent notamment d'étudier :
  • fichiers ;
  • registre ;
  • processus ;
  • threads ;
  • réseau ;
  • DLL ;
  • handles.
Sous Windows, Process Monitor et Process Explorer sont très utilisés.

Désassembleurs

Un désassembleur transforme du code machine en assembleur lisible.

Le programme contient réellement des octets.

Par exemple :

Code: Select all

48 89 C8
peut correspondre à une instruction comme :

Code: Select all

mov rax, rcx
Le désassembleur tente donc de donner une représentation textuelle des instructions processeur.

Exemples d'outils :
  • Ghidra ;
  • IDA ;
  • Binary Ninja ;
  • radare2.
Debuggers

Un debugger permet d'exécuter le programme sous contrôle.

Il permet notamment :
  • de mettre des breakpoints ;
  • de faire du step into ;
  • de faire du step over ;
  • d'observer les registres ;
  • de lire la mémoire ;
  • de voir la pile ;
  • de suivre les appels ;
  • d'arrêter l'exécution à une adresse précise.
Sous Windows, x64dbg est très populaire pour le reverse user-mode.

WinDbg est particulièrement puissant pour Windows et le kernel.

Décompilateurs

Un décompilateur tente de reconstruire du pseudo-code haut niveau à partir du binaire.

Exemple :

Code: Select all

mov eax, ecx
imul eax, eax, 2
add eax, 5
ret
peut devenir quelque chose comme :

Code: Select all

return value * 2 + 5;
Il faut cependant rester prudent.

Le pseudo-code n'est pas le code source original.

Le décompilateur peut :
  • se tromper sur les types ;
  • mal identifier certaines structures ;
  • mal nommer les variables ;
  • mal reconstruire certaines conditions.
Le reverse engineer doit donc toujours être capable de revenir à l'assembleur.

10. Différence entre désassembleur, debugger et décompilateur

Désassembleur

Il transforme le code machine en assembleur.

Code: Select all

machine code
   ↓
assembleur
Debugger

Il exécute le programme sous contrôle et permet d'observer son état.

Code: Select all

programme en cours d'exécution
   ↓
registres / mémoire / pile / breakpoints
Décompilateur

Il tente de reconstruire un pseudo-code plus proche du C/C++.

Code: Select all

code machine
   ↓
analyse
   ↓
pseudo-code
Ces trois outils sont complémentaires.

11. Analyse statique et analyse dynamique

Analyse statique

Le programme n'est pas exécuté.

Avantages :
  • vue globale ;
  • analyse des fonctions ;
  • navigation dans les références ;
  • observation des chaînes ;
  • lecture des imports.
Inconvénients :
  • certains comportements n'apparaissent qu'à l'exécution ;
  • les données peuvent être calculées dynamiquement ;
  • le programme peut être obfusqué.
Analyse dynamique

Le programme est exécuté dans un debugger ou surveillé par des outils.

Avantages :
  • on voit les vraies valeurs ;
  • on observe les branches réellement prises ;
  • on suit la mémoire ;
  • on confirme rapidement une hypothèse.
Inconvénients :
  • on ne voit qu'un chemin d'exécution à la fois ;
  • certaines fonctions peuvent ne jamais être exécutées ;
  • le programme peut détecter le debugger.
Dans la pratique, les deux méthodes sont souvent utilisées ensemble.

12. Le rôle de la curiosité et du raisonnement

Le reverse engineering demande beaucoup de raisonnement.

Il faut constamment poser des questions :
  • pourquoi cette valeur est-elle chargée ?
  • d'où vient ce pointeur ?
  • pourquoi cette fonction est-elle appelée ?
  • que représente cette structure ?
  • pourquoi cette comparaison existe-t-elle ?
  • qu'est-ce qui déclenche cette branche ?
Le reverse engineer construit des hypothèses puis les vérifie.

Exemple :

Code: Select all

cmp byte ptr [rax], 0
je end
Hypothèse :

Code: Select all

rax pointe peut-être vers une chaîne
et le programme teste le caractère '\0'
Il faut ensuite vérifier en mémoire si cette hypothèse est correcte.

13. Reverse engineering et légalité

Le reverse engineering n'est pas automatiquement illégal.

La légalité dépend :
  • du pays ;
  • du logiciel ;
  • de l'objectif ;
  • du contrat de licence ;
  • des protections techniques ;
  • des droits d'auteur ;
  • des secrets industriels.
Copyright

Le copyright protège notamment le code source et certaines parties d'un logiciel.

Comprendre le fonctionnement d'un programme n'est pas forcément équivalent à copier son code.

Secrets industriels

Certaines informations techniques peuvent être considérées comme des secrets industriels.

La manière dont ces informations ont été obtenues peut avoir une importance juridique.

Brevets

Certaines techniques peuvent être protégées par des brevets.

Le reverse engineering peut révéler leur fonctionnement, mais cela ne signifie pas nécessairement que leur reproduction soit autorisée.

Contrats de licence

Certaines licences logicielles contiennent des clauses interdisant ou limitant le reverse engineering.

Il faut donc vérifier les conditions d'utilisation du logiciel étudié.

DMCA

Aux États-Unis, le Digital Millennium Copyright Act contient notamment des dispositions concernant le contournement de certaines protections techniques.

Il existe cependant des exceptions et des situations particulières, notamment autour :
  • de l'interopérabilité ;
  • de la recherche en sécurité ;
  • de certains audits ;
  • de certaines activités réglementées.
Le cadre juridique exact doit toujours être vérifié selon le contexte.

14. Ce qu'un reverse engineer cherche réellement

Lorsqu'il analyse un programme, le reverse engineer cherche rarement à comprendre chaque instruction.

Le but est surtout d'identifier les éléments importants.

Par exemple :
  • où commence la logique intéressante ;
  • quelles fonctions traitent les données ;
  • où sont effectuées les vérifications ;
  • quelles API sont appelées ;
  • quelles structures sont manipulées ;
  • où sont les entrées utilisateur ;
  • où se trouvent les décisions importantes.
Un bon reverse consiste donc à filtrer l'information.

Dans un gros programme, des millions d'instructions peuvent être exécutées.

Il faut apprendre à retrouver les quelques centaines ou quelques dizaines d'instructions réellement intéressantes pour l'objectif recherché.

15. Exemple simple de démarche

Imaginons un programme qui affiche :

Code: Select all

Access denied
On peut commencer par rechercher cette chaîne dans le binaire.

Puis suivre les références vers cette chaîne.

On peut tomber sur quelque chose comme :

Code: Select all

cmp eax, 1
jne denied
call success
jmp end

denied:
lea rcx, "Access denied"
call print
On peut alors reconstruire :

Code: Select all

if (result == 1)
{
    success();
}
else
{
    print("Access denied");
}
Cet exemple illustre parfaitement le principe du reverse :

Code: Select all

information visible
   ↓
référence dans le programme
   ↓
code assembleur
   ↓
reconstruction de la logique
16. Ce qu'il faut retenir

Le reverse engineering consiste à comprendre un programme existant sans nécessairement disposer de son code source.

Il existe deux grandes approches complémentaires :
  • observer le comportement du programme ;
  • analyser directement son code.
Les connaissances les plus importantes sont :
  • assembleur ;
  • architecture processeur ;
  • systèmes d'exploitation ;
  • compilation ;
  • mémoire ;
  • debugging.
Les principaux outils sont :
  • outils de monitoring système ;
  • désassembleurs ;
  • debuggers ;
  • décompilateurs.
Le désassembleur montre les instructions.

Le debugger montre leur exécution réelle.

Le décompilateur tente de reconstruire une logique haut niveau.

Un bon reverse engineer ne lit pas simplement les instructions une par une.

Il cherche à comprendre :
  • les données ;
  • les décisions ;
  • les fonctions ;
  • les interactions système ;
  • les algorithmes ;
  • la structure générale du programme.
Enfin, le reverse engineering possède un cadre légal qui dépend du contexte.

Avant d'analyser ou de modifier un logiciel tiers, il faut toujours vérifier les droits, licences et lois applicables.

Conclusion

Le reverse engineering est une discipline située entre le développement bas niveau, l'architecture processeur, les systèmes d'exploitation et l'analyse logicielle.

L'assembleur fournit le langage permettant de lire ce que le processeur exécute réellement.

Les outils de monitoring permettent d'observer le programme depuis l'extérieur.

Les désassembleurs permettent de lire son code.

Les debuggers permettent de suivre son exécution.

Les décompilateurs permettent d'obtenir une représentation plus abstraite.

Mais l'outil le plus important reste le raisonnement.

Le but n'est pas seulement de savoir lire :

Code: Select all

mov
cmp
jmp
call
ret
Le véritable objectif est de pouvoir regarder ces instructions et comprendre progressivement :

Code: Select all

"voilà ce que le programme est en train de faire"
C'est à partir de cette compétence que commence réellement le reverse engineering pratique.

Who is online

Users browsing this forum: No registered users and 1 guest