Changement d'architecture 1/2 (Reverse) - 404CTF 2022

Changement d'architecture 1/2 (Reverse) - 404CTF 2022

Fichiers du challenge Principe:
Retrouver un mot de passe (en explorant les registres et la stack d'un interpréteur de 'VM')

Le challenge

Nous allons devoir trouver le mot de passe d'un programme (dispo ici) écrit dans un langage "propriétaire" , sous forme d'une VM interprétée

Le zip a été ici réduit à son strict minimum. L'ensemble des fichiers est disponible à cette addresse , incluant la partie glibc (que j'aurai pu simplifier, mais tant pis)

Lancement

En réutilisant les scripts de Fourchette , on peut lancer la VM: il va nous falloir trouver un mot de passe

Evidemment, le mot de passe n'est pas aisément trouvable directement dans la source de vm_data.bin, mais cela vaut le coup d'y jeter un oeil, pour essayer de repérer des patterns, des strings, etc

Je patch alors l'ELF pour qu'il soit lançable sans passer par un script sh

Données vs instructions

Une première approche peut consister à modifier un peu le contenu du fichier vm_data pour trouver quels octets sont des données

L'idée était ici de modifier tous les octets: si la VM plante, c'est que l'octet fait partie des instructions (ou des données "critiques", comme une longueur de buffer). Si la VM ne plante pas, c'est que l'octet fait partie des données simples, par exemple, les caractères d'un message affiché ("Pass?") ou les caractères du mot de passe

Un tour dans le débugger

Comme l'altération des octets de vm_data ne m'a pas mené loin, je suis parti chercher les sources de glibc, pour le compiler et lancer la VM sans devoir réutiliser les scripts de Fourchette

Je vais donc chercher les sources sur leur FTP

On pouvait utiliser la libc fournie par le challenge, mais je me suis dit que j'aurai peut-être besoin de hooker quelques fonctions pour faciliter le taff, donc, je suis parti sur la recompilation. Dans ce cas, pwninit ou bien glibc all-in-one + patchelf peuvent être utilisés

Il suffit ensuite de suivre le guide du build (dans Get started/build the lib/with intent to install)
On lance alors la VM, avec le script de la libc qui nous concerne

Notez que je suis maintenant dans ~/ctf: en effet, la libc a eu quelques soucis avec le chemin que j'utilise habituellement, probablement à cause des espaces et des parenthèses. Je vous recommande donc de faire de même

On peut alors lancer la VM, et attacher le débugger (edb) dessus pour essayer de comprendre le fonctionnement de l'interpréteur (vm)
On pourra alors prendre "note" des bytes du fichier de données de la VM qui sont altérables, sans tout planter

N'ayant pas trouvé grand chose d'utile ainsi, je passe rapidement

Notez que certains octets pourraient être altérables car ils ne sont jamais "exécutés" (parce qu'un JUMP les as évités ou autre)

Le nombre d'instructions

L'idée suivant était de prendre GDB (plutôt qu'edb car GDB est scriptable), de lancer la VM, d'entrer un mot de passe, et de compter le nombre d'instructions jusqu'à la fin du programme. Avec de la chance, le nombre d'instruction va varier en fonction de l'entrée, révélant peut-etre le mot de passe lettre par lettre, ou au moins, sa longueur

On lance la VM d'un côté (droite) et on lance GDB de l'autre (gauche) en l'attachant à cette VM
Si on utilise ni, alors la VM s'arrête immédiatement dans tous les cas

Il faut en pratique utiliser si, car ni va exécuter les call comme une seule instruction, là où si n'exécutera qu'une seule instruction machine ("step into", en gros)

On utilise si et on automatique la méthode pour changer la première lettre du mot de passe: toutes les exécutions se sont avérer faire le même nombre d'instructions machine!

A ce stade, on sait que la comparaison entre l'entrée et le mot de passe ne se fait donc pas lettre à lettre directement. Une possibilité serait que la longueur du mot de passe soit d'abord comparée à l'entrée, et qu'ensuite leurs contenus soient comparés

Longueur du mot de passe

On applique la même approche, et on fait varier la longueur du mot de passe (note: oui, j'ai dû refaire ces screens)
On observe alors qu'à chaque groupe de 8 caractères, il se passe un "truc", de même pour abcdefghijklmnopqrstuvwx (24 chars)
Au-delà de 24 caractères, le nombre d'instructions ne bouge plus

On peut donc partir de l'hypothèse qu'il faut 24 caractères pour le mot de passe, ce qui ne sera pas facilement brute-forçable, sauf à être lettre-par-lettre

Reverse? pour de vrai?

Histoire de faire propre, j'ai voulu tenter un vrai reverse: la "table" d'instruction se trouve facilement en faisant défiler les instructions dans edb
On retrouve, dans le vm_data, les "instructions" en question
Histoire de faciliter les choses, on peut désactiver l'ASLR pou que les instructions assembleur restent à la même place
En pratique, j'ai trouvé ça fastidieux…
A part repérer que les instructions 0fcaXXXX servent de "JUMP" vers l'offset XXXX, je n'ai pas voulu aller plus loin
J'ai quand même constaté qu'un "JUMP" vers b801 permet d'atteindre le "GG"

Tracing

XOR

Je change alors d'approche, et je "trace" l'exécution de la VM à la recherche, dans les registres, du mot de passe

En pratique, je ne m'attends pas à trouver le mot de passe directement dans un registre, mais je compte trouver ce que j'ai entré comme mot de passe dans les registres, et une transformation/chiffrement de cette entrée avant d'être comparée au mot de passe stocké dans le vm_data

On voit ici que 8 caractère de mon entrée sont dans un registre: comme on l'avait anticipé lors de la recherche de la longueur du mot de passe, la VM marche en 64 bits (en blocs de 8 octets)

En analysant le résultat du "trace", on voit que le début du mot de passe entrée est XOR-é avec 0xabababababababab
Ce qui donne 0xc4c3dfdec7d3cec7 (attention à l'endian!)
Dans la stack, à droite, on trouve la valeur f4d89ac3fff4d8e2 qui, une fois XORée avec le 0xabababababababab trouvé donne Is_Th1s_: on a le début du mot de passe!

Test du premier bloc

On change tous les jumps 0fca0404 par 0fcab801 jusqu'à tomber sur "GG": on saura alors quel JUMP teste la longueur du mot de passe (celui ligne 0x210)
On remet ce jump à 0404 et si on entre 24 caractères, on a "GG", preuve que ce bloc testait la longueur du mot de passe
Maintenant, de même, on change un autre JUMP et on teste avec le début du mot de passe trouvé, jusqu'à avoir GG

On a alors réussit à "déjouer" 2 des 4 blocs contenant un jump 0fca0404: la longueur du mot de passe et la valeur du premier bloc de 8 caractères de ce mot de passe. Les deux autres testent sans doute les deux autres morceaux du mot de passe (qui fait 3 x 8octets)

On retrouve la valeur XORée dans le vm_data, peut-être trouvera-t-on une autre valeur XORée pour les autres morceaux de mot de passe?

Encore du XOR

En traçant de nouveaux l'exécution, on peut chercher d'autres XOR et les registres associés

Notez qu'on trouvera des instructions du type Rx XOR Ry avec Rx et Ry, identiques. Cela permet en fait de mettre la valeur d'un registre à 0, et ce n'est donc pas en lien avec ce qu'on recherche.

On observe, dans l'un des registre du trace, la valeur _??!!???

Cette valeur est intrigante car elle apparait alors qu'on retrouve le cbe7a5e2decbe7bd précent dans l'autre registre

Et un XOR avec f4d89ac3fff4d8e2 donne ce _??!!???, aurait-on trouvé quelque chose?

En pratique, j'ai mis 5 bonnes minutes à tilter que le début du mot de passe étant Is_Th1s_, soit "is this", la fin du mot de passe pourrait bien être un point d'interrogation… ou plusieurs! donc _??!!??? est une fin de mot de passe crédible!

On teste et oui! C'est la fin du mot de passe!

On a donc déjoué 3 des 4 blocs de test: la longueur, le XOR du premier bloc, le XOR du dernier bloc, et il ne nous manque plus que le bloc central de 64 bits

Dernier bloc

Un brute force?

Histoire de gagner (peut-être) du temps, j'ai essayé de brute-forcer un peu le milieu du mot de passe avec des trucs crédibles, mais ça n'a rien donné

Un autre XOR?

Comme je suis une feignasse, j'ai re-tracé l'exécution du programme, dumpé les registrer à chaque instruction si, et XOR-é tous les registres entre eux, espérant trouver un bloc qui ferait un morceau de mot de passe correct

J'ai retrouvé, dans ces XORs, les morceaux Is_Th1s_ et _??!!??? d'ailleurs, ce qui m'aurait permis de flagguer plus vite peut-être

Une autre opération?

J'ai refait de même avec d'autres opérations: + n'a rien donné, mais - a fait apparait 4rM_LiTe! voilà une candidat crédible!
On remet ces trois morceaux ensembles et yes, le mot de passe est le bon! Flag:
404CTF{Is_Th1s_4rM_LiTe_??!!???}

Fichiers du challenge

↩ Retour à la liste des challenges

⇇ Retour à l'accueil