Résoudre un crack-me simple avec Cutter

Utilisation de l'outil de reverse engineering Cutter pour résoudre un crackme.

Dans ma quête d’apprentissage du reverse engineering, je suis tombé sur ce github avec quelques crack-me en x64 dont le niveau augmente au fur et à mesure.

Le niveau 1 étant trop simple, je me suis concentré sur le niveau 2 pour ce writeup.

Après avoir cloné le repo, on se retrouve avec les crack-me en c.

1_cHXqDOzQQuuW6Oky3aOsoQ.png

Pour le compiler et ainsi le désassembler (car analyser le code en C ne serait pas drôle), on lance la commande make  :

1_9yqDPJo9oeR0QlKidtbBhQ.png

On se retrouve ainsi avec un fichier .64, et la commande file nous donne quelques informations supplémentaires :

On confirme ainsi que c’est un ELF x64, not stripped, ce qui est normal pour un crack-me facile comme celui-ci.

Exécutons ce binaire et voyons ce qu’il nous donne !

1_rttdl6nLnBfpgNiwDtzI-A.png

On voit que le binaire n’a besoin que d’un seul argument, on va donc essayer un string simple pour commencer : “aaaa”

1_K-RH_gFkgRVppJ5VbdKHdg.png

A ma plus grande surprise, le string “aaaa” ne fonctionne pas, mais on sait que l’on pourra trouver en décompilant ce binaire la condition d’échec en recherchant cette chaîne.

On va enfin démarrer Cutter et inspecter ce binaire plus en profondeur.

1_BmlieUeeD-BHXd-1mg9dqQ.png

On utilise l’analyse basique pour commencer.

La première chose que je fais lorsque j’analyse un binaire est de jeter un œil aux fonctions du programme. On voit ici 2 fonctions entry mais dans ce genre de crack-me, elles ne sont pas utilisées. C’est toujours un bon réflexe de les analyser.

1_87xdJNNljH1MINPIOj-gZA.png

On voit une fonction main, qui sera celle analysée ici.

La deuxième chose que j’ai l’habitude de faire, c’est d’analyser les strings du programme, pour voir un peu ce que contient le binaire, les strings d’échec et de réussite.

1_goQspBrXG3cO04_PiIzpCg.png

On voit premièrement les fonctions utilisées (ce qui peut être très intéressant dans le cadre d’un binaire avec strcmp par exemple, pour trouver le mot de passe comparé), et ensuite on retrouve les strings qu’on a vu lors de l’exécution du programme :

1_QhMEMQU3ap4nnM-KK0r5Og.png

Password1 ! C’est surement le mot de passe du binaire.

1_BO3YB9Lz-e2tKSF7EqOb0g.png

Raté, ce serait trop simple..

Assez parlé, on regarde enfin ce que fait cette fameuse fonction main.

1_amSH-1nPnUpGYQvXAy78Kw.png

La première chose qui me saute aux yeux est la présence d’une boucle (la flèche verte suivant le jump à 0x1163), juste après la validation de la comparaison (cmp eax, edx). Ce que je comprends ici c’est qu’il y a plusieurs conditions de validité qui doivent être remplies avant d’aboutir à la réussite du programme.

On peut également confirmer la présence d‘une boucle avec l’incrémentation du registre rcx à 1 à chaque itération, ce qui correspond à un compteur.

1_ygfhqPa01TMPYzZQnHYhWA.png

En regardant le decompiler (ce que je fais quasi jamais car je ne comprends pas grand chose en c), on confirme une bonne fois pour toutes la présence d’une boucle avec “while”

L’analyse statique ne nous donne pas vraiment de moyen de deviner le mot de passe, on va lancer le debug en se concentrant sur ce bloc, avec une comparaison qui, si elle est réussie (les valeurs sont égales), rebouclera.

1_vD8MownqoLoN7qkTIcHvZg.png

Voici le bloc qui nous intéresse.

Je lance le debug avec password1 comme argument, on va essayer de trouver pourquoi ce mot de passe ne fonctionne pas.

1_SfpLstlzlxTydeLc5m03cw.png

Nous sommes prêts ! Je me suis placé au début de la fonction main et on va passer rapidement les lignes qui ne nous intéressent pas.

1_jsRTddp3waj9pNqgj_HOow.png

Avant de commencer, à la deuxième instruction on voit que le programme vérifie que l’on a mis un argument, en comparant edi (le pointeur) avec 2. rdi étant à 2, le programme continue.

1_Tdddz_10xTepm40zhyxxKQ.png

Le registre rax est chargé avec la valeur hexadécimale de p.

Première instruction intéressante, on voit que le programme met ‘p’ dans le registre eax, et remet à zéro le registre ecx, qui va nous servir de compteur. On voit également que l’offset du string ‘password1’ testé plus tôt est chargé dans rdi.

1_t38CK3z675oC7pth17M6zA.png

Autre bloc intéressant on voit que le registre edx est chargé avec la valeur de rsi + rcx (rsi étant le regsitre contenant notre mot de passe, et rcx le compteur). On a donc un test du premier index de notre chaîne, soit argc[0].

On voit aussi un test dl, dl qui jump à la fin du programme (la fin réussie !) si la valeur des bits de poids faible de rdx = 0. On sait donc que la chaîne à deviner termine par 0, ce qui arrête la boucle.

1_LSFmfGMsBFxpNEGC7AAKKg.png

Stop! eax a été décrémenté et contient maintenant la valeur 6f, ce qui correspond à ‘o’, le programme compare ensuite cette valeur avec la première lettre du mot de passe que l’on a renseigné, contenue dans rdx.

1_YpNNFChJNMi7imFF8ujvtw.png

Il fallait s’en douter.. ‘o’ n’étant pas égal à ‘p’ le programme a tout de suite break et nous affiche que le mot de passe est incorrect.

Relançons donc le programme avec ‘o’ comme première lettre, pour analyser ce qui se passe dans ce cas là.

1_nDTmMXbVhvnFkfLHtTH3yA.png

C’est reparti

1_A0CmSgeIG0BOto6w4pzn0Q.png

J’ai sauté toutes les précédentes étapes car on sait déjà ce qui se passe, on retourne un peu avant notre comparaison. En jetant un œil à nos registres, rdx (notre mot de passe) est à 6f (‘o’, la première lettre). Voyons la comparaison après la décrémentation de eax.

1_9KiamJ1IAJcM4w9Epq0tYw.png

Enfin un nouveau bloc! rax et rdx étant égaux on rentre dans la boucle, rcx s’incrémente à 1 (on peut traduire par “1 comparaison a été faite”). Ensuite, eax va changer et passer à rdi + rcx (rdi étant l’offset du mot de passe “password1”, chargé au début du programme +1, le compteur, on comprend donc eax = chaine_a_trouver[1]).

Encore une fois, le test al, al qui termine le programme si le résultat est 0, confirme une dernière fois que la chaîne à trouver se termine par 0.

1_G0awdE3fdQjJE15DACzbUg.png

On reboucle ensuite sur ce bloc (qui a déjà servi auparavant) et on voit ici que c’est au tour d’edx d’être incrémenté par le compteur. On comprend donc que la prochaine valeur à tester sera argc[1], soit la lettre ‘a’ de notre argument ‘oassword1’.

1_TVFdIPZypA6tBjgcRaCbuQ.png

eax se décremente encore et contient désormais la valeur 60 soit “`"

La je ne comprends plus trop, je croyais au départ qu’a chaque boucle, l’index la chaîne “password1” hardcodée dans le binaire changeait, mais de manière aléatoire. C’est à dire qu’on aurait pu se retrouver avec un mot de passe comme “srwaps1od”, mais la une quote est contenue dans eax, qui ne correspond à aucune lettre de la chaîne “password1”.

Je me souviens ensuite qu’on a la certitude que le chaîne termine par 0, on a donc un changement de valeurs des caractères de la chaîne à chaque itération.

1_j07-f4pjrwbLnIv8nE6Z_A.png

Pas de surprise, ‘a’ n’étant pas égal à “`”, le programme break

Ilaurait été possible de résoudre ce crack-me en relançant le binaire une dizaine de fois avec chaque fois la lettre comparée de eax, en découvrant à chaque itération le prochain caractère et ainsi modifier notre argument pour que la condition soit validée.

Mais avec un peu de réflexion, je remarque que le code hex (soit 60) dans rax correspond au code ASCII 96 du caractère “`”, et, surprise, juste en bas, on se retrouve avec le caractère “a” précédemment chargé dans eax avant d’être décrémenté :

1_fMQr7v5FjKGEZIQdrWNXKA.png

J’en conclus donc qu’a chaque boucle, le binaire compare l’index i (ici représenté par le registre rcx) de l’argument avec celui du mot de passe -1, mais pas -1 dans l’index, -1 dans la valeur héxadécimale, ce qui va faire changer le code ASCII du caractère de la chaîne.

Je me retrouve donc à décomposer la chaîne “password1” en la réécrivant avec la valeur précédente du tableau ASCII. Par exemple, p, qui a une valeur ASCII de 70, se retrouve à 6f, soit la valeur de o.

A noter que “o” était en effet le premier caractère du mot de passe qu’on avait trouvé.

En décomposant cette chaîne j’arrive à “o`rrvnqc0”, ce qui doit être le mot de passe du binaire.

1_0_YjW0RoaT1e3NH_aYBaQQ.png

Ici le “\” permet de passer une quote dans un argument

Great! Le mot de passe est donc o`rrvnqc0

Merci d’avoir lu ce premier write-up, qui, mine de rien m’aura demandé quelques heures à rédiger. On se retrouve très bientôt pour le crack-me n3 de cette série.

Crack-me : https://github.com/NoraCodes/crackmes

Cutter : cutter.re

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
Généré avec Hugo
Thème Stack conçu par Jimmy