Un fichier sans code source
Une entreprise trouve un petit outil sur son propre serveur, et personne ne se souvient de qui l’a écrit. Il n’y a pas de code source, seulement le programme. Avant de le lancer, quelqu’un doit lire ce qu’il fait. C’est la rétro-ingénierie : comprendre comment un programme fonctionne à partir du programme lui-même.
À la fin de cette leçon
Remarque
Je peux utiliser un outil pour savoir ce que fait un programme sans son code source.
La règle à retenir
Remarque
Ce lab sert à lire comment fonctionne un programme, comme le fait un défenseur ou un étudiant en compilation. Utilise-le sur les programmes de ton propre lab et sur rien d’autre. Il n’enseigne jamais, et tu ne dois jamais l’employer pour : des logiciels malveillants, contourner la licence d’un vrai logiciel, des générateurs de clés, cacher un programme à un débogueur, ou attaquer un programme qui n’est pas le tien. Au Royaume-Uni, c’est un délit au regard du Computer Misuse Act 1990, et partout ailleurs la loi a ses propres règles.
Mots-clés
- Binaire : un programme tel que l’ordinateur l’exécute, un fichier d’instructions machine.
- Désassembleur : un outil qui retransforme les instructions machine en assembleur lisible.
- Débogueur : un outil qui exécute un programme, l’arrête où tu le décides et montre sa mémoire.
- Registre : une toute petite mémoire à l’intérieur du processeur. Sur cette machine,
rdicontient le premier argument d’un appel. - Appel de bibliothèque : un appel à du code déjà écrit, comme
puts, qui affiche du texte. - Appel système : une demande d’un programme au système d’exploitation, par exemple pour écrire.
Pour commencer : ce que tu sais déjà
Remarque
Réponds dans ta tête. 1. Qu’est-ce que le code source ? 2. Que fait un compilateur de ce code ? 3. Qu’est-ce qu’un argument de fonction ? Tu as besoin de la troisième réponse dans un instant.
Avant de commencer
Remarque
Appuie sur Démarrer le lab dans la barre en haut de cette leçon. Ce lab est une machine Ubuntu entière, rien que pour toi : elle met un peu plus de temps à démarrer qu’un conteneur. Chaque bouton Exécuter tape sa commande dans le terminal de la machine, l’une après l’autre, dans la même session. Si l’un d’eux dit que la machine démarre encore, attends quelques secondes et appuie de nouveau. Pour les regarder, ou pour taper les tiens, appuie sur Ouvrir le terminal dans la barre.
Un binaire est un fichier qu’on peut décrire
Le lab range ses programmes dans le dossier revlab. hello ne cache aucun secret : il affiche une ligne. La commande file dit de quel genre de fichier il s’agit, sans l’exécuter. Remarque ELF, le format des programmes sous Linux, le processeur pour lequel il a été construit, et not stripped, qui veut dire que les noms de ses fonctions sont toujours dedans.
file revlab/helloAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Strings trouve le texte à l’intérieur d’un programme
Un programme porte son texte en lui, lisible, que quelqu’un ait voulu le montrer ou non. strings affiche chaque suite de caractères lisibles, ce qui fait une longue liste : la commande ne garde donc que la ligne qui contient Hello. Tu découvres ce que dit le programme sans l’exécuter.
strings revlab/hello | grep HelloAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Vérifie
Astuce
D’où viennent les mots, alors que tu n’as jamais lancé le programme ? Réponse : de l’intérieur du fichier : un programme porte son texte avec lui.
Une erreur fréquente
Attention
Beaucoup pensent que compiler un programme cache ce qu’il dit. Compiler change la forme, pas le fond : le texte, les noms des appels et l’ordre des étapes sont toujours là. Tout ce qu’un programme doit connaître pour fonctionner, un lecteur peut le trouver : ne garde donc jamais un secret dans un programme.
Un désassembleur montre les instructions
objdump -d transforme les instructions en assembleur, une par ligne. La commande ne demande que la fonction main et garde les lignes qui parlent de puts : il y en a une, un call vers la fonction de bibliothèque qui affiche. radare2 fait le même travail et ajoute des noms. aa lui demande d’analyser le fichier, pdf @ main affiche la fonction. Cherche le nom puts et le texte qu’on lui donne.
objdump -d revlab/hello --disassemble=main | grep putsAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
r2 -q -e scr.color=0 -c 'aa' -c 'pdf @ main' revlab/hello 2>/dev/nullAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Un débogueur arrête le programme et montre sa mémoire
Lance le programme sous gdb et arrête-le à la première ligne de puts. Selon la façon dont un appel fonctionne sur cette machine, puts trouve l’adresse de son texte dans le registre rdi. x/s $rdi veut dire : montre la chaîne (string) à cette adresse. Tu lis ainsi l’argument d’une fonction pendant que le programme est en pause, comme tu liras un mot de passe.
gdb -batch -ex 'break puts' -ex 'run' -ex 'x/s $rdi' revlab/helloAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Tracer montre les appels que fait un programme
ltrace affiche chaque appel de bibliothèque avec ses arguments, et strace chaque appel système. Le premier montre puts recevant le texte. Le second montre le programme demandant au système d’exploitation d’écrire les mêmes octets à l’écran, avec write. Le message est envoyé nulle part ici pour que seule la trace s’affiche. Aucun des deux n’oblige à lire de l’assembleur.
ltrace revlab/helloAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
strace -e trace=write revlab/hello >/dev/nullAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Maintenant, fais le lab
Astuce
Tu as regardé un programme inoffensif avec chaque outil. Ton lab a cinq programmes, chacun cachant une clé différente sur ta copie, et tu liras chacun avec l’outil qui lui convient. Ouvre le fichier README.md de ton dossier personnel avec cat README.md dans le terminal, et écris les clés que tu trouves dans answers.txt.
Vérification finale
Astuce
Réponds sans regarder en arrière. 1. Quel outil liste le texte lisible d’un programme ? 2. Quel outil permet d’arrêter un programme en cours et de lire sa mémoire ? 3. Quelle est la différence entre un appel de bibliothèque et un appel système ? 4. Pourquoi un secret dans un programme n’est-il jamais vraiment secret ? Réponses : strings ; un débogueur, gdb ; un appel de bibliothèque va vers du code déjà écrit, un appel système va vers le système d’exploitation ; parce que n’importe qui peut lire ce qui est dans le fichier.
Pour les enseignants
Astuce
Cette leçon est un petit exemple de leçon donnée dans un lab : les étapes et les boutons Exécuter sont la leçon, et le lab est la copie de chaque élève. Elle se déroule dans un lab VM : chaque élève a sa propre machine Ubuntu, et un bouton Exécuter tape dans son terminal et montre ce qui s’est affiché. Points clés : 1. On peut lire un programme sans son code source. 2. Le texte et les appels restent dans un programme compilé. 3. Un débogueur montre la mémoire d’un programme en cours. 4. Tracer montre les appels que fait un programme. Erreur fréquente : croire que compiler cache ce que fait un programme. Matériel : le lab et un navigateur. Conseil : demandez à la classe de dire ce que fait hello avant tout outil, puis vérifiez chaque hypothèse. Le lab lui-même a une vérification qui compare les clés de answers.txt avec celles produites pour chaque copie. C’est de la lecture défensive des seuls programmes du lab. Copiez la leçon dans votre propre module pour la modifier, ou construisez-en une de zéro avec le menu Lab du générateur de leçons.