Cracking : The Beginning

To the non-french speaker, note that you can translate the articles using the Google Trad widget situated at the bottom of all pages.


Dans cet article nous allons voir les bases du reverse engineering. Pour cela j’ai crée un petit crackme.

Avant de lancer le crackme vous devrez lui attribuer des droits d’écriture, de lecture et d’exécution. Pour des questions d’habitude, je dépose toujours le fichier sur mon Bureau, donc il faudra que je lance cette commande depuis le répertoire Bureau :

chmod u+x crackme1

Ok maintenant on va pouvoir lancer le crackme :

./crackme1

start.png

Le but du jeu est donc de trouver le mot de passe.

Si vous entrez un mauvais mot de passe vous obtiendrez ce message :

fail.png

Et si vous avez le bon mot de passe vous aurez ceci :

success.png

Bien, on va tout de suite faire tomber le suspens, je vais vous montrer le code source du programme histoire de pouvoir comparer le code C et le code assembleur.

code.png

Analysons le un peu. Déjà on peut voir que le mot de passe est stocké en dur dans un tableau de caractères. Ensuite le programme récupère la valeur envoyée par l’utilisateur et la stocke dans le tableau input. Puis input et string sont comparés grâce à la fonction strcmp. Si le résultat de la comparaison vaut 0 alors cela veut dire que l’utilisateur a entré le bon mot de passe. Sinon c’est qu’il s’est trompé.

Au cours de cet article nous allons voir trois méthodes pour le cracker :
-Par l’inspection de la mémoire
-Par la modification d’un jump
-Par l’altération des flags

Pour suivre cet article vous n’aurez besoin que de gdb qui est un débogueur Unix. Si vous êtes sous Windows vous pourrez travailler avec Ollydbg ou windbg mais les commandes ne seront pas les mêmes. Pour ma part j’utilise Peda (une surcouche gdb) que vous pouvez télécharger en entrant les commandes suivantes dans un terminal :

git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit

I/ Inspection mémoire

Dans cette première partie nous allons voir comment récupérer le mot de passe juste en observant la structure du programme ainsi que la mémoire qui lui est allouée. Comme je vous l’ai dit plus haut nous allons nous servir de gdb. Pour lancer le débogage sur notre crackme il faudra entrer la commande suivante :

gdb crackme1

Vous devriez obtenir ceci :

accueilgdb.png

La première chose que je fais quand je lance gdb c’est de le forcer à utiliser la syntaxe Intel qui est plus « propre » que la syntaxe AT&T (selon moi). Pour cela on entre la commande suivante :

set disassembly-flavor intel

On sait que notre programme commence par un main donc ce qui serait bien c’est de commencer le débogage par la première instruction du main. Pour cela on va poser un breakpoint au niveau du main.

Comment fonctionne les breakpoint ? En fait c’est gdb qui va rajouter une instruction de type interruption à l’adresse où nous posons le breakpoint. Une fois le breakpoint posé on pourra examiner le contenu des registres ainsi que des flags et faire toutes sortes de modifications.

Pour poser un breakpoint au niveau du main, on utilise la commande « b » suivie du mot clé main (qui désigne la fonction main) :

breakmain.png

Notre breakpoint est posé on va donc pourvoir lancer le débogage pour cela on utilise la commande :

run

Et voilà ce que l’on obtient si on utilise peda gdb :

run.png

L’écran est divisé en quatre parties. La première partie (celle du haut) nous permet d’avoir un aperçu direct des registres ainsi que des flags. La partie du milieu (sous le —–code—–) nous monte le contenu de la section code avec les différentes instructions. La partie en dessous nous montre l’état de la pile et la dernière partie nous permet d’entrer les commandes mais aussi de voir le résultat des précédentes.

Donc là vous pouvez voir que gdb s’est bien arrêté au breakpoint 1 situé à l’adresse 0x000055555555473e (en vérité l’adresse mémoire est la suivante : 0x55555555473e, il semblerait que peda rajoute les quatre 0 mais je ne saurais vous dire pourquoi … :P) qui correspond à l’entrée du main. A partir de là on va demander à gdb de désassembler le code afin que l’on puisse l’examiner. Pour cela on utilise la commande suivante :

disass

deassembler.png

En général c’est à ce moment, quand on commence l’assembleur, qu’on sent qu’on va passer un sale moment ahah ! Comme nous l’avons vu dans l’article sur les bases du cracking, on retrouve des instructions qui agissent sur une ou plusieurs opérandes. Bien évidemment, je ne vais pas m’amuser à détailler le fonctionnement de chaque instructions. Cependant dès qu’on en utilisera une je vous expliquerais comment elle fonctionne.

La première chose qu’on remarque c’est les annotations  entre «  » qui nous spécifie le nom des fonctions qui sont appelées : puts, printf, strcmp et scanf. Comme on possède le code source du crackme on sait que le scanf correspond au moment où on demande à l’utilisateur de rentrer le mot de passe. Les deux printf, quant à eux, correspondent au message qui s’affiche au début du jeu « Bienvenue blabla… » et au message de réussite ou d’échec qui s’affiche à la fin.

Oh et on retrouve la fonction strcmp() que nous avons utilisé pour gérer la comparaison dans le code C ! Oh mais attends, on sait que la comparaison se fait dans cette fonction donc ça serait cool de voir ce qu’il s’y passe nan ?

Effectivement ça peut être intéressant ! Pour se faire nous allons placer un nouveau breakpoint juste au niveau de l’appel à la fonction strcmp, c’est à dire ici :

strcmp2.png

Et la commande pour placer le breakpoint sera :

 b *main+93

On place un breakpoint à l’adresse située 93 adresses plus bas par rapport au main. Puis on entre la commande « c » pour continuer le débogage :

b2.png

Ok ce que j’ai peut être oublié de vous dire c’est que lorsque vous déboguez avec gdb le programme que vous déboguer est exécuté en live. C’est pour ça que sur l’image ci dessus vous voyez le début du programme.D’ailleurs quand on fera de l’analyse de malware il faudra être extrêmement précautionneux puisque le malware sera exécuté en temps réel !

A ce niveau là on va entrer un mot de passe bidon comme… Heu je sais pas…. Test ? De toute façon on s’en fout 😉 !

breakpoint2.png

Nous voici donc au niveau de notre deuxième breakpoint c’est à dire au niveau de l’appel de la fonction strcmp(). L’instruction qui appelle la fonction strcmp est l’instruction call. Je ne vais pas vous expliquer comment elle fonctionne dans cet article puisque ce sera le sujet du prochain article. En revanche ce que je peux vous affirmer c’est que lorsque l’instruction call est utilisée, très souvent les paramètres utilisées par la fonction appelées sont placés dans des registres et ce juste avant l’appel.

Si on regarde les instructions qui se déroulent juste avant le call on se rend compte qu’il y a 4 instructions qui manipulent des données  :

4i.png

Si vous avez suivi l’article sur l’introduction au cracking vous devez savoir que l’instruction lea permet de charger l’adresse mémoire du registre entre crochet dans la première opérande. Autrement dit, les deux première instructions récupèrent des adresses mémoires depuis…. Eh bien pour le moment je ne vais pas vous le dire. Encore une fois l’explication vous sera donnée dans le prochain article.

Puis on voit qu’il y a des déplacements de données de registres en registres grâce à l’instruction « mov ». Et comme je vous l’ai dit les paramètres utilisés par une fonction sont très souvent manipulés juste avant l’appel de la fonction.

Donc il serait peut être judicieux de vérifier le contenu du registre rdi. Pour cela on va utiliser la commande suivante :

i r $rdi

La commande « i r » est le raccourci de « info register » qui nous donnent donc des informations sur les registres :

ir.png

Erf il n’y pas notre mot de passe… Dégoûte nan ?… Attendez ! C’est tout à fait logique en fait !! En effet on a vu plus haut que l’instruction « lea rax,[rbp-0xd] » charge dans le registre eax une adresse mémoire. Puis avec l’instruction « mov rdi,rax » on « mov » l’adresse mémoire du registre rax dans le registre rdi. Donc au final l’adresse que l’on récupère via le « i r $rdi » est celle qui a été chargé par l’instruction lea. Et comme on ne sait pas -pour le moment- d’où vient cette adresse mémoire, il serait bon de voir ce qu’elle contient.

En utilisant la commande :

 x 0x7fffffffe0c3

On va pouvoir voir ce qui est contenu à l’adresse mémoire 0x7fffffffe0c3 :

res.png

On retrouve bien le mot de passe qui permet de valider le challenge ! GGWP !!

  • Ce qu’il faut retenir de cette première partie :
    -Il faut toujours s’aider des annotations
    -Si possible il faut repérer où se font les comparaisons et les tests
    -Ne pas hésiter à checker régulièrement le contenu des registres. Parfois la solution est juste sous nos yeux !

II/ Manipulation des jump

La deuxième technique va être de modifier l’instruction jump située à l’adresse 0x00005555555547a3.

Dans le code source nous avons fait en sorte que si le mot de passe entré par l’utilisateur correspond au mot « password » alors le message de succès est affiché. Dans le cas contraire c’est le message d’échec qui est affiché.

En assembleur l’instruction qui permet la mise en place de ce genre de condition est l’instruction jump (jmp). Comme nous l’avons dans l’article sur les bases du cracking, il y a différents types de jump. Ici nous avons affaire à un « jne » qui se traduit par « jump on not equal ». Et juste avant le jump nous avons l’instruction cmp qui permet de comparer un registre avec un autre registre, une constante ou une valeur stockée en mémoire.

L’instruction cmp va en fait procéder à une soustraction entre l’opérande  1 et l’opérande 2. Si les deux opérandes sont identiques alors le flag Zero Flag (ZF) sera activé (bit à 1). Sinon il ne le sera pas (bit à 0).

Ensuite nous avons le jne qui comme je l’ai dit correspond à un jump if not equal et oh… ça tombe bien, le jne se sert bizarrement du flag ZF pour savoir s’il doit jump ou pas !

Si je rentre un mauvais mot de passe le ZF n’est pas activé :

zf.png

Donc le programme nous renvoie ça :

fail.png

Ce qu’il va falloir faire ici c’est changer le type du jump, le passer de jne (jump on not equal) à je (jump on equal), de cette manière on va modifier le sens du jump et peut être pouvoir jumper directement au message de succès sans avoir le bon mot de passe :

Pour changer l’instruction « jne » en « je » on utilise la commande suivante :

set{char}0x00005555555547a3=0x74

Avec 0x74 la valeur hexadécimal du « je ».

Et, si on continue le débogage :

success.png

GGWP again 🙂 !

III/ Modification des flags

La dernière solution que j’ai à vous proposer rejoint la deuxième puisqu’elle va se baser sur l’utilisation du Zero Flag. Nous avons vu que le « jne » jump au message de succès si le flag ZF était activé (bit à 1), donc ce qu’on peut faire c’est modifier l’état de ce flag pour forcer le jump.

Pour cela on va tout d’abord afficher l’état des flags avant le call :

flags.png

Les seuls flags activés sont le parity flag, le signed flag et le interrupt flag. Nous ce que nous voulons c’est activer le ZF. Alors on pourrait tenter de faire un :

set $ZF =1

Mais vous verrez que ça ne fonctionne pas.

Rappelez vous, je vous avais dit que les flags étaient stockés sur 32 bits pour les processeurs 32 bits, et 64 bits pour les processeurs 64 bits. Voici les différents flags dont nous disposons :

eflags.png
Crédit image : Wikipédia

Nous, nous ne voulons activer que le ZF qui est situé sur le 6ème bit. Donc on veut que ce bit et seulement celui-ci soit à 1. Si on regarde une table de conversion, on voit que 0100 0000 = 0x40. Donc si on entre cette commande :

 set $eflags=0x40

Et qu’on affiche les flags :

flags.png

On a bien le Zero Flag activé. Et maintenant si on continue le débogage :

ggwp.png

Encore gagné 🙂 !

  • Ce qu’il faut retenir de la partie deux et trois :
    -On peut très facilement modifier les instructions
    -On peut aussi jouer avec les flags

Voilà pour ce premier vrai article sur le cracking ! J’espère qu’il vous aura intéresser et que vous y aurez appris des choses. Je sais qu’il y a pas mal de questions que vous devez vous poser notamment sur la fameuse instruction qui charge une adresse d’on ne sait où mais je vous promets que toutes vos questions auront une réponse dans le prochaine article 😉 !

4 commentaires

  1. Super article très pedagogique.Merci pour tout et continue comme ça!

    Si je comprends bien le ZF est utilisé pour toutes les comparaisons ?
    Donc en théorie, dès qu’il y a une comparaison, on peut toujours la bypassé en modifiant le ZF suivant l’emplacement où on se situe dans l’execution du programme. C’est bien ça?

    On peut également modifier le ZF comme ceci :
    set $eflags = $eflags | 64
    où 64 correspond à 2^6 ou 6 est le bit corespondant au ZF.
    Source : https://grim7reaper.rolinh.ch/blog/2013/11/09/crackme-anti-debug/

    Sur cette pages, on peut voir à quoi correspond les autres flags :
    https://fr.wikibooks.org/wiki/Programmation_Assembleur_x86/Les_flags

    interressant tout ça 🙂

    J'aime

    1. Salut Olivier, tout d’abord merci pour ton commentaire, ça fait plaisir 🙂 !

      Donc pour ta question, oui le ZF est un des flags qui est modifié lorsque l’instruction cmp est jouée (ce n’est pas le seul). Donc en jouant avec tu vas pouvoir bypass des comparaisons. Et pour savoir comment le modifier, il faudra te référer à l’instruction suivante (le jump). Si le jump est de type jz (jump on zero) alors en activant le ZF tu jumperas. Si le jump est de type jnz (jump on not zero) alors il faudra laisser le bit du ZF à 0.

      Merci pour l’info 🙂 !

      J'aime

Répondre à Defte@WhiteFlag Annuler la réponse.

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google

Vous commentez à l'aide de votre compte Google. Déconnexion /  Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s