Avant-propos
Nous allons à travers cette solution résoudre l’épreuve Guessy
en réalisant une analyse statique du fichier via l’outil Radare2
.
Reconnaissance
Avant tout, nous commençons par déterminer le type du fichier qui nous est fourni :
$ file guessy
guessy: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped
On voit qu’on a affaire à un éxecutable, mais rien de bien probant. Essayons alors de lancer le programme pour voir ce qu’il fait :
chmod +x guessy
Ainsi le programme semble attendre de nous qu’on lui fournisse le flag, voyons ce qu’il se passe si on lui fournit le peu qu’on connaisse du drapeau attendu :
Super ! Maintenant il ne nous reste plus qu’à deviner les 8 prochains caractères du drapeau. Malheureusement, je ne suis pas magicien et le brute force n’est pas autorisé.
Alors faisons les choses proprement et lançons notre analyse statique avec Radare2
.
Analyse statique via Radare2
Nous commençons la rétro-ingénierie de notre éxecutable en précisant l’option aaaa
pour révéler le plus de détails possibles de l’architecture du programme.
1. Fonction main
On affiche ensuite le code désassemblé de la fonction main
:
On voit dans la fonction que le programme attend une entrée de l’utilisateur via la fonction fgets
et que suite à ça une fonction sym.validate
est appelée pour vraisemblablement valider l’entrée de l’utilisateur.
Il n’y a rien d’autre de très intéressant, nous passons alors à l’analyse de la fonction sym.validate
.
2. Fonction sym.validate
Voyons ce qu’elle contient :
On se retrouve face à un bon nombre de lignes mais on peut grossièrement en tirer le fonctionnement principal de la fonction.
On peut voir que la fonction effectue 5 comparaisons à l’aide de l’instruction cmp
.
-
Si celles-ci échouent le programme saute alors à l’instruction située en
0x40150b
qui affiche le message “Well it does not begin well for you.” avant de sauter à la fin du programme. -
Si elles réussissent on passe à celle d’après, sauf pour la dernière qui en cas de réussite saute à l’instruction située en
0x401532
et qui n’est autre qu’un appel de la fonctionsym.difficul_part
. Ce qui nous intéressera fortement pour la suite.
On remarque qu’à chaque fois, la comparaison est opérée entre le registre al
qui semble à tour de rôle prendre la valeur de chaque caractère fourni par la dernière saisie utilisateur et une valeure décimale.
Une horloge doit alors faire tic dans votre esprit : allons voir la table des caractères ASCII.
Nous retrouvons alors la correspondance :
Décimal | Symbole |
---|---|
70 | F |
67 | C |
83 | S |
67 | C |
123 | { |
C’est bien le début du drapeau que nous recherchons ! C’est bon signe pour la suite, nous avons décelé la façon dont sont vérifiés les caractères saisis par l’utilisateur.
Cette partie étant faite, attaquons-nous à la suite de l’énigme : la fonction sym.difficult_part
.
3. Fonction sym.difficult_part
Nous désassemblons la fonction et observons ce qu’elle fait :
Ouch ! Le désassemblage nous présente 256 lignes de code, analysons-le par morceaux.
Caractères 6-13
Voici le premier bloc d’instructions situés juste après la déclaration des variables.
Premièrement, on remarque que le message “Now you can try to guess the next eight characters of the flag” est affiché comme vu à l’étape de reconnaissance et qu’après ça une saisie est attendue de l’utilisateur via la fonction fgets
.
On peut voir qu’en cas d’une saisie plus grande que 9 octets (8 caractères + caractère nul de fin de chaîne), le message Well it seems that someone has trouble counting to eight est affiché et le programme est arrêté.
Pour ces 8 caractères, la logique utilisée est la même que pour la première étape, alors après un rapide coup d’oeil à notre bonne vieille table des caractères ASCII, nous retrouvons la correspondance :
Décimal | Symbole |
---|---|
101 | e |
55 | 7 |
53 | 5 |
53 | 5 |
50 | 2 |
99 | c |
102 | f |
54 | 6 |
Notre flag avance ! Pour l’instant nous avons : FCSC{e7552cf6
.
Caractères 14-21
Maintenant, jetons un oeil au prochain bloc :
Cette fois, on peut voir qu’il y a une instruction en plus avant chaque comparaison : add eax, eax
.
Cette instruction a pour effet d’additionner le registre à lui-même avant que celui-ci ne soit comparé aux valeurs affichées en rouge.
Sachant que le registre eax
contient à tour de rôle les 8 caractères saisis par l’utilisateur, c’est leur valeur qui est multipliée par deux avant d’être comparée.
Ainsi pour obtenir les caractères du drapeau, rien de plus simple, il nous suffit juste de diviser par deux les valeurs visibles en rouge dans r2
.
Après une recherche de correspondance dans la table ASCII nous obtenons donc ces valeurs :
Décimal | Symbole |
---|---|
52 | 4 |
55 | c |
101 | e |
50 | 2 |
101 | e |
53 | 5 |
102 | a |
100 | d |
Une fois de plus notre flag avance ! Désormais nous avons : FCSC{e7552cf64ce2e5ad
.
Caractères 22-29
Voyons ce que nous réserve le prochain bloc :
Cette fois ce n’est pas une addition qui est réalisé mais un décalage (shift) de 3 bits vers la gauche via l’instruction : shl eax, 3
.
Le triple décalage vers la gauche des bits aura pour effet de multiplier la valeur du registre par 8 (2^3).
Ainsi en inversant le calcul, il nous faut diviser par 8 les valeurs attendues pour obtenir les caractères du drapeau.
De cette manière, nous obtenons :
Valeur décalée | Valeur attendue | Symbole |
---|---|---|
384 | 48 | 0 |
784 | 98 | b |
784 | 98 | b |
384 | 48 | 0 |
456 | 57 | 9 |
424 | 53 | 5 |
416 | 52 | 4 |
816 | 102 | f |
Nous y sommes presque ! Pour l’instant, notre flag est : FCSC{e7552cf64ce2e5ad0bb0954f
.
Caractères 30-37
Le bloc se présente ainsi :
Cette fois nous sommes confrontés à un xor
logique effectué entre les variables testées à l’étape précédente et nos caractères saisis.
Or comme le dit le dicton : le xor de mon xor est mon xor, donc pour retrouver le bout de flag, rien de plus simple : il suffit de faire le xor entre les valeurs attendues (en rouge) et nos variables précédentes (0bb0954f).
Pour cela, on sort python :
previous = b"0bb0954f"
expected = b"\x01\x54\x55\x51\x09\x07\x57"
res = bytes([x^y for x,y in zip(previous, expected)])
Ce qui nous donne b'167a02cf'
et donc pour le flag : FCSC{e7552cf64ce2e5ad0bb0954f167a02cf
.
Ainsi notre flag est complet… ou presque !
Un appel est fait à la fonction sym.most_difficult_part
à la fin du programme, nous ne sommes pas au bout de nos peines…
Dernière étape
Jetons-y un coup d’oeil :
Ouf ! C’était une blague, il manquait simplement l’accolade fermante (}
) de code ASCII 125
, comme nous l’indique le “Can you guess the LAST character of the flag ?” et l’instruction de comparaison en 0x0040118e
.
Validation
Notre drapeau est désormais complet : FCSC{e7552cf64ce2e5ad0bb0954f167a02cf}
.
Rentrons-le désormais dans le programme pour vérifier :
L’épreuve est terminée.