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, 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 :

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 :


Il ne nous est pas possible de détailler ici toutes les instructions. Nous ne présenterons que les plus utilisés :

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 :

IDA PRO

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 :

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.

Ollydbg

Quelques outils pratiques pour aller plus loin :

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…