Introduction à la rétro-ingénierie logicielle
En pratique
Rappel d'assembleur
Il est indispensable de maitriser l’assembleur pour faire de la rétro-ingénierie. Le principal travail de cette discipline est en effet de lire du code assembleur et de le comprendre.
Les registres
Les registres sont des emplacements mémoires
situés dans le processeur ou il est possible de stocker des
données. Ces emplacements mémoires sont
très utilisés, car ils sont très
rapides. Les principaux registres sont les suivants :
- EAX: Extended Accumulator Register
- EBX: Extended Base Register
- ECX: Extended Counter Register
- EDX: Extended Data Register
- ESI: Extended Source Index
- EDI: Extended Destination Index
- EBP: Extended Base Pointer
- ESP: Extended Stack Pointer
- EIP: Extended Instruction Pointer
EAX,
EBX, ECX et EDX sont les
registres de travail.
EDI et ESI sont
utilisés principalement pour des
instructions d'adressage, mais peuvent aussi servir de registres de
travail.
EBP et ESP sont des
registres de pile. ESP
pointe vers le sommet de la
pile. EBP
permet de travailler dans la pile sans devoir utiliser ESP
qu'il ne vaut mieux pas modifier pour de pas avoir de
problèmes d'alignements de la pile.
EIP
désigne l'adresse de l'instruction en cours
d'éxecution.
Les Flags
Le registre de flags est un registre particulier dont chaque
bit correspond à un état. Les flags permettent de
connaître l’état
d’opérations arithmétiques
effectuées par le processeur. Les flags peuvent donc avoir
la valeur 0 ou 1 ( c’est un bit). Le 1 signifie que le flag
est levé. Les principaux flags sont les suivants :
- Z-flag : pour Zero-flag, c’est le plus utilisé en « reversing »,il est mis 1 si le résultat d'une instruction arithmétique a donné 0.
- O-flag : pour Overflow-flag, sert pour marquer un débordement.
- C-flag : pour Carry-flag, il est positionné si le résultat nécessite une retenue.
- S-flag :pour Sign Flag, il est positionné lorsque le résultat de l'opération est négatif.
La pile
La pile est une structure de données de type LIFO ( Last in First out ) dans laquelle il est possible d’empiler et de dépiler des données. Le sommet de la pile est indiqué par le registre ESP.Les instructions principalement utilisées pour la manipulée sont "push" et "pop".Elles permettent respectivement d'empiler et de dépiler.
Les instructions :
Une instruction (ou opcode ) est une opération élémentaire qu'un programme demande à un processeur d'effectuer. Le jeu d’instruction dépend du type de processeur. Nous ne nous intéresserons ici qu’aux processeurs de type x86.
Les
instructions s’utilisent de la manière suivante :
- add eax,ebx ;; Registre, Register
- add eax,123 ;; Register, Value
- add eax,dword ptr [401000] ;; Register, Dword Pointer [value]
- add eax,dword ptr [eax] ;; Register, Dword Pointer [register]
- add eax,dword ptr [eax+00401000] ;; Register, Dword Pointer [register+value]
Il ne nous est pas possible de détailler ici toutes les
instructions. Nous ne présenterons que les plus
utilisés :
- NOP ou no operation : cette instruction ne fait rien.
- ADD destination, source : additionne le contenu de source dans destination.
- SUB destination, source : soustrait le contenu de source à destination.
- MOV destination, source : Cette instruction copie la valeur de source dans destination.
- INC destination : Cette instruction incrémente destination d'une unité.
- DEC destination : Cette instruction décrémente destination d'une unité.
- JMP label : Met la valeur désignée par le label dans EIP ( la prochaine instruction sera celle pointée par label).
- PUSH source : Place la valeur de source au sommet de la pile.
- POP destination : Place la valeur au sommet de la pile dans destination.
- CMP destination,
source
: Cette instruction effectue la soustraction de destination et source, mais ne
conserve pas le résultat. Le Zero Flag est
positionné à 1 si le résultat de la
soustraction est nul.Le Carry Flag est positionné
à 1 si le résultat ne tient pas sur
32bits.l’ Overflow Flag est positionné
à 1 si le résultat a provoqué un
débordement.
Les sauts conditionnels sont exactement comme JMP à l’exception que leur comportement dépend de l’état des registres :
Opcode | Signification | Saute si |
JE | Jump if equal | ZF=1 |
JG | Jump if (signed) greater | ZF=0 and SF=OF (SF = Sign Flag) |
JGE | Jump if (signed) greater or equal | SF=OF |
JL | Jump if (signed) less | SF != OF |
JLE | Jump if (signed) less or equal | ZF=1 and OF != OF |
JMP | Jump | saute toujours |
JNA | Jump if (unsigned) not above | CF=1 or ZF=1 |
JNC | Jump if carry flag not set | CF=0 |
JNE | Jump if not equal | ZF=0 |
JNZ | Jump if not zero | ZF=0 |
JZ | Jump if zero | ZF=1 |
Les Outils
Trois grandes familles d’outils sont utilisées dans la rétro-ingénierie. Encore une fois, cette liste est toute sauf exhaustive.
Les
Désassembleurs :
L’un des principaux outils utilisés en
rétro-ingénierie est le désassembleur
qui traduit du langage machine en langage assembleur du code machine
binaire compilé et illisible. Une fois le code
désassemblé il nous “suffira”
de le lire pour comprendre le fonctionnement du programme.
C’est ce que l’on appel une analyse statique.
Les désassembleurs les plus connus sont les suivants :
- Ollydbg un des meilleurs debogueurs et désasembleur
- IDA Pro le meilleur désassembleur payant. Néanmoins les anciennes versions sont disponibles en « freeware »

Les
Décompilateurs
Le décompilateur est un outil qui va un petit peu plus que
le désassembleur. Il sert à reconstituer,
partiellement ou totalement, le code source du logiciel à
partir d’un programme executable dans son langage de
programmation initiale. Cette opération n’est
jamais parfaite, car de nombreuses sont informations sont perdues
à la compilation, car le compilateur optimise le code.
Néanmoins pour les langages comme Java ou C# qui utilisent
un langage intermédiaire le résultat est
très intéressant. Les décompilateurs
les plus utilisés sont les suivants :
- JAD : pour le java
- Reflector : pour le C#
- SWFScan pour le flash
- Etc...
Les
Débogueurs
Les Débogueurs permettent de suivre
l’exécution du programme pas à pas et
de contrôler l’exécution du programme en
cours fonctionnement. C’est ce que l’on appelle
l’analyse dynamique. Il est ainsi possible de pauser des
points d’arrêt sur le programme à des
moment stratégique comme lors de l’ouverture
d’une fenêtre ou la lecture d’un fichier
ce qui évite d’avoir tout le code de
l’application à lire. Le Débogueur le
plus utilisé est Ollydbg
qui en plus d’être excellent et doté de
très nombreux plug-ins est gratuit. C’est
l’outil indispensable pour commencer la
rétro-ingénierie.

Quelques outils pratiques pour aller plus loin :
- Peid : pour détecter les « packers » les plus connus.
- Imprec : pour reconstruire la table des imports (IAT)
- LordPE : pour dumper des processus en mémoire.
Les protections
Nous allons présenter ici les principales protections
couramment utilisées afin de limiter/ralentir
l’analyse d’un programme. Il est important de
comprendre qu’aucune protection n’est parfaite.
Tant que le programme s’exécute sur la machine, il
aura une méthode pour comprendre son comportement.
Le coût de mise en place d’une bonne protection est
non négligeable, c’est pourquoi il faut trouver un
bon ratio entre les protections choisies et le manque à
gagner en cas de copie illégale du logiciel.
Protection par serial/Nag
Afin de limiter l’utilisation d'une logiciel il est fréquent de voir apparaître une protection par numéro de série ou une fenêtre de limitation au lancement de celui-ci. Bien qu’elle soit relativement simple à mettre en place pour les développeurs elle reste assez simple à contourner. De plus si les algorithmes de génération de serial sont trop faibles il sera aisé pour une personne mal intentionnée de diffuser un générateur de numéro de série.
Obfuscation de code
L’obfuscation de code consiste à rendre le code le plus illisible possible afin de rendre sa compréhension très complexe. Cette méthode ralentira considérablement l’analyse du programme. Il est très intéressant d’appliquer ce genre de protection sur du code dont le langage est interprété et dont la source est donc plus facile à récupérer. Ex de code Perl obfusqué ( oui on peut faire pire que le code de base…):
Les "packers"
Les "Packers" sont des programmes dont le fonctionnement consiste à compresser un programme et parfois à le chiffrer simultanément. Au moment de l’exécution du programme « packé » celui-ci se décompressera et se déchiffrera en mémoire avant de s’exécuter normalement. Ce genre de protection est très efficace.Techniques « antidebug »
Afin d’analyser un programme, l’utilisation de certains outils comme un déboguer est quasiment incontournable. Ce type de protection consiste à détecter l’utilisation d’un déboguer sur le programme et d’en modifier alors le comportement. Il est ainsi possible, par exemple, de vérifier le temps d’exécution du programme et ainsi de détecter la présence d’un débogueur. Des méthodes de l’API Windows permettent également de vérifier si le programme s’exécute seul ou si un debogueur est attaché à lui. Placées dans des endroits stratégiques du programme, ces vérifications sont difficiles à détecter et peuvent facilement décourager des personnes peu déterminées
Machine virtuelle
Le programme est alors exécuté dans une machine virtuelle. L’analyse ne se portera donc plus sur le programme lui-même, mais sur la machine virtuelle exécutant celui-ci. Il est alors extrêmement complexe d’analyser le programme. Ce genre de protection est généralement vendu à des prix très élevés, mais garantit un très bon niveau de sécurité. De plus, les protections commerciales de ce type allient d’autres techniques afin de complexifier encore l’analyse.
Toutes les protections présentées précédemment ne sont pas parfaites et ne ralentiront qu’un certain temps une personne déterminée. Pour être vraiment efficaces, elles devront être combinées les unes avec les autres et être parfaitement intégrées au design de l’application. En effet, il ne sert à rien de protéger juste une DLL de vérification de numéro de série s’il est possible dans le programme principal de rediriger son appel vers notre propre dll…