Souk (Reverse) - FCSC 2022

Souk (Reverse) - FCSC 2022

Fichiers du challenge Principe:
Tester puis placer les caractères du flag en comptant les JMP pris

Le challenge

Le challenge nous donne un binaire (dispo ici) et nous demande d'en tirer ses secrets

Mot de passe

En le lançant, ce binaire demande un mot de passe
On creuse donc son exécution via edb

Mot de passe ou flag?

En essaynt avec un mot de passe et en allant pas à pas (via F7)…
… on arrive vers la string "Bravo tu as r[éussi]"

Ici, j'ai remplacé les instructions de JMP avant cette string par des "nop": les jumps n'existant plus, ils ne seront pas pris et j'arriverai dans la suite de l'exécution

Et le challenge nous dit que le mot de passe sera en fait le flag

Cette première étape marche parfois différemment: en accédant à la partie après vérification du mot de passe, le challenge aurait pu déchiffrer le flag et nous le donner. Il est donc utile de faire toujours ce genre de vérification.

Brute force

On tente alors avec un flag dont le format est plausible (puisque tous les flags démarrent par FCSC)
J'ai alors essayé le brute force, mais l'espace serait trop grand et le temps trop long

Tout brute-forcer est impossible: 64 caractères, 16 possibilités par caractères, 64**16 combinaisons…
Un brute force caractère par caractère (64 * 16) est en revanche plausible

Char par char

On essaie alors un flag de zéros, en espérant que certains jumps soient pris (là où se trouvent réellement des zéros)
On trouve alors une instruction "JE" qui parfois est prise, parfois pas
On tente le coup en postulant que si elle n'est pas prise, alors un zéro se trouve là
On essaie alors avec 16 flags dont le contenu complet va de 0 à f

D'ailleurs, on voit que le jump JE esquivant le "Bravo" est pris si DWORD RBP-0x38 vaut zéro, ce qui arrive si le jump précédemment trouvé JE 0x55...bd est pris: on peut postuler que si ce jump est pris, le caractère est mauvais

On connait alors toutes les lettres du flag (on doit en retrouver 64 au total, ce qui est bon) et leur "position" dans le flag mélangé

Par exemple, on a mis le flag FCSC{0...0}, et le JUMP a été non-pris 4 fois: c'est que le flag contient 4 "0", qui sont à la bonne place dans le flag mélangé (03 35 40 45), Mais comme le flag a été mélangé, on ignore leur position dans le flag d'origine

Master mind

Vous vous rappelez de ce jeu? Où il faut deviner la place de pions de couleurs sur une grille? On va un peu procéder pareil, mais de manière très simpliste, même si peu efficace

Breakpoints

Pour faciliter la suite, on va désactiver l'ASLR, pour que les addresse des instructions soient constantes

L'ASLR sera remis au prochain reboot, donc, pas besoin de penser à le ré-activer. On peut aussi faire:
echo 0|sudo tee /proc/sys/kernel/randomize_va_space

Bon, en revanche, edb ne garde pas les breakpoints d'un run à l'autre, donc, ca va être fastidieux…

Si c'était à refaire, je le ferai maintenant via gdb: j'écrirai un fichier du type:
b *0x555...\nr < flag-guess.txt et on compte le nombre d'arrêts (c pour continue)

Un décalage progressif

J'ai donc pris un flag dans lequel j'ai mis les bons caractères (dans un ordre arbitraire) et j'ai compté le nombre de "bien placés"

Impossible de connaitre facilement la position des "bien placés" dans le flag d'origine car "Souk" a en fait mélangé les lettres du flag d'abord, et retrouver cet ordre me semblait…compliqué

Toutefois, je pense qu'on pouvais retrouver le mapping de ce mélange en entrant un flag où tous les caractères sont uniques, et en regardant dans quel ordres ils sont testés par le JUMP. Mais je n'ai pas appliqué cette piste

J'ai ensuite refait de même en décalant le contenu du flag d'un octet à chaque fois

Pour ne pas polluer inutilement ma liste de "bien placéss", j'ai remplacé le FCSC{} par zzzzzz ce qui est forcément "mal placé"

A partir de la différence pos:0a 0f 12 41 (avant décalage) et pos:0a 12 41 44 (après décalage), j'en déduis que pos:0f n'est plus bien placé, et que pos:44 est maintenant bien placé.
Or, pos:0f est un char:e et pos:44 est un char:7
Alors, je cherche un char:e qui est devenu autre chose ("bien placé" perdu), et un char:X qui aurait changé pour être remplacé par un char:7 ("bien placé" gagné)
J'en déduis sont la valeur de 2 caractères du flag e et 7

Et on décale de nouveau d'un cran, en procédant de même. Le flag se remplit peu à peu

On peut sans doute déduire plus d'informations à chaque décalage, mais bon, tant pis

Pour aller plus vite, j'ai parfois décalé de 2 crans, mais cela complexifie la déduction: ici, j'ignore quel 1 est bien placé
Mais en continuant les décalages, je peux faire la déduction à l'étape d'après

Flag

Après un long moment, le flag est complet:
FCSC{665cfa3e0277a889258cc9f6e24c88fc9db654178558de101b8a19af8fb00575}

Je pense que après l'expérience de ce CTF et du 404CTF, je ferai autrement: je scripterai gdb ./souk|tee scanme.log pour tester chacund des 64x16 caractères possibles, un à un. En comptant les JUMP pris/non pris dans la sortie de GDB, j'en aurai déduis si mon N-eme caractère est bon et j'aurai brute-forcé le flag char par char automatiquement

PS

Si on ne redémarre pas, on peut remettre l'ASLR en place, mais c'est facultatif

Fichiers du challenge

↩ Retour à la liste des challenges

⇇ Retour à l'accueil