Defcon 2015 résolution heapsoffun

La semaine dernière, plusieurs ingénieurs de SCRT ont participé aux qualifications du CTF Defcon avec l’équipe 0daysober, qui a terminée 10ème et se qualifie donc pour la finale ! Ce post décrit deux des épreuves résolues, knockedup et heapsoffun.

Heapsoffun

If you have been knockedup then you know what to do. Perhaps try "tirer"
sha1sum heapsoffun 5ee5b2cde811e617cd789c73c1d8d2d9e8b27c36
Yes we know the flag is owned by root.

Un challenge pwnable de 4 points faisable après knockedup.

tldr; un challenge de reverse de 1 point permettant d’ouvrir 2 ports en fonction d’une séquence de paquets envoyée sur des ports UDP.

knockedup

Le nom annonce la couleur, un challenge de port knocking.
Le binaire utilise libpcap et sniff les paquets sur l’interface qui lui est passée en paramètre --interface Un coup d’œil aux chaînes de caractères permet de voir une commande ajoutant une règle iptables :

/bin/bash -c "/sbin/iptables -I KNOCKEDUPD 1 -s %IP% -p tcp --dport 10785 -j ACCEPT"

Un point d’arrêt sur pcap_compile() permet d’obtenir le filtre dans RDX et étonnamment, rien d’autre n’était nécessaire pour résoudre l’épreuve.

RDX: 0x60a4e8 ("((udp dst port 13102 or 18264 or 18282) or (udp dst port 31717 or 35314 or 39979 or 15148 or 14661))")

Une connexion udp sur chaque port du premier groupe permet d’exécuter la règle iptables et d’obtenir le flag en TCP sur le port 10785.

echo pouet | nc -u -w 1 knockedupd_71a592a753bf9dcd7d7ad5fa69b2bab3.quals.shallweplayaga.me 13102
echo pouet | nc -u -w 1 knockedupd_71a592a753bf9dcd7d7ad5fa69b2bab3.quals.shallweplayaga.me 18264
echo pouet | nc -u -w 1 knockedupd_71a592a753bf9dcd7d7ad5fa69b2bab3.quals.shallweplayaga.me 18282
nc knockedupd_71a592a753bf9dcd7d7ad5fa69b2bab3.quals.shallweplayaga.me 10785
The flag is: 'Kn0ck kn0ck, Wh0 it is?'

knockedup -> heapsoffun

Quel est le lien entre cette épreuve et heapsoffun ?

Le second groupe de ports n’a pas été utilisé pour obtenir le flag, c’est donc là qu’il intervient.

Si l’on regarde les chaînes de caractères en unicode on trouve -j ACEPT"dport98KNODU15s%Ic qui attire l’attention mais ne signifie rien. Les cross references d’IDA aideront à retrouver où sont utilisés ces caractères qui sont bien évidemment remis dans l’ordre pour donner une seconde commande iptables.

Un point d’arrêt sur la dernière lettre concaténée permet de retrouver la chaîne complète.

break *0x402333
r --interface eno1
RDI: 0x60c448 --> 0x60c628 ("/bin/bash -c " /sbin/iptables -I KNOCKEDUPD 1 58 -s %IP% -p tcp --dport 9889 -j ACCEPT")
echo pouet | nc -u -w 1 knockedupd_71a592a753bf9dcd7d7ad5fa69b2bab3.quals.shallweplayaga.me 31717
echo pouet | nc -u -w 1 knockedupd_71a592a753bf9dcd7d7ad5fa69b2bab3.quals.shallweplayaga.me 35314
echo pouet | nc -u -w 1 knockedupd_71a592a753bf9dcd7d7ad5fa69b2bab3.quals.shallweplayaga.me 39979
echo pouet | nc -u -w 1 knockedupd_71a592a753bf9dcd7d7ad5fa69b2bab3.quals.shallweplayaga.me 15148
echo pouet | nc -u -w 1 knockedupd_71a592a753bf9dcd7d7ad5fa69b2bab3.quals.shallweplayaga.me 14661
nc knockedupd_71a592a753bf9dcd7d7ad5fa69b2bab3.quals.shallweplayaga.me 9889
>

En se connectant cette fois sur le port 9889 en TCP après avoir envoyé des paquets sur les ports UDP du deuxième groupe du filtre on obtient une sorte de shell où l'indice Perhaps try "tirer" prend tout son sens.

En effet, en envoyant la commande tirer, un binaire nous est envoyé, celui de l'épreuve heapsoffun !

heapsoffun

Un pwnable x86_64 PIE attendant diverses commandes :

accumuler, bilan, update, remove, select, randoms, norandoms, toggle

En vue du nom de l'épreuve et des commandes, l'exploitation va passer par un heap massage mais ni la fonction malloc ou free n'est utilisée. C'est donc un allocateur "maison" qu'il va falloir reverser et exploiter.

accumuler prend une taille et une chaîne de caractères et permet de l'allouer :

> accumuler
    Length: 10
    Data: abcdefghij
    Received: 10

bilan permet d'afficher les chaînes allouées :

> bilan
    Id: 0
    Length: 10
    Data: abcdefghij

update prends un id, une position et une valeur et permet de remplacer l'octet à la position souhaitée de la chaîne id par la valeur souhaitée :

> update
    Index: 0
    Byte: 0
    Value: 66
> bilan
    Id: 0
    Length: 10
    Data: Bbcdefghij

chr(66) == 'B'

remove prend un id et supprime la chaîne :

> remove
    Index: 0
> bilan

select, randoms et norandoms ne servent à rien pour la heap "maison" et nous reviendrons sur toggle qui ne demande aucun paramètre et n'affiche rien.

toggle

La commande toggle alloue une jump table de 56 octets utilisant le même allocateur et y inscrit les adresses des fonctions des 7 commandes (pas toggle). C'est alors ces pointeurs qui seront utilisés par la suite pour exécuter les commandes.

Vulnérabilité

Exceptée cette pratique dangereuse de stocker les pointeurs de fonction à coté des datas contrôlées par l'utilisateur, la vulnérabilité permettant d'exploiter cela provient de la commande update.

Lorsque l'on indique le Nème octet à remplacer, la chaîne de 3 caractères maximum est transformée en int par la fonction atoi. La fonction vérifie alors que cet entier signé est bien inférieur à la taille de la chaîne de caractères.

Il est alors possible d'y écrire un nombre négatif et d'aller modifier un octet situé avant notre chaîne.

heap

L'allocateur utilise des zones de 1036 octets demandées via mmap et organise les objets à l'aide de meta-datas.

Les meta-datas placées avant contiennent entre autre un pointeur sur la chaîne de caractères.

Lorsqu'une zone de 1036 octets ne suffit plus pour recevoir la prochaine chaîne de caractères, le programme en demande une nouvelle de 1036 pour y allouer l'objet.

Exploitation

Nous avons donc tous les éléments pour exploiter le programme.

  • Jump Table

On commence par appeler la commande toggle afin d'allouer en premier les pointeurs que nous allons exploiter. Puis nous allouons un objet que nous utiliserons plus tard pour réécrire un de ces pointeurs et qui est donc situé juste après la jump table.

  • Leaker la jump table

Les différentes commandes utilisent les meta-datas pour afficher ou mettre à jour les chaînes de caractères. Utiliser la même méta-data pour mettre à jour l'adresse de ces même méta-datas risque de poser des problèmes. Nous utilisons donc 2 objets supplémentaires pour leaker.

0x7fcf89a8a090: 0x00007fbea21920a4 0x0000000000000010
0x7fcf89a8a0a0: 0x00000000
0x7fcf89a8a0a4: 0x6161616161616161 0x6161616161616161
0x7fcf89a8a0b4: 0x0000000000000010 0x0000001000000000
0x7fcf89a8a0c4: 0x00000000
0x7fcf89a8a0c8: 0x00007fcf89a8a0dc 0x0000000000000010
0x7fcf89a8a0d8: 0x00000000
0x7fcf89a8a0dc: 0x6161616161616161 0x6161616161616161
0x7fcf89a8a0ec: 0x0000000000000314 0x0000000000000000

Avec le second, nous allons modifier l'adresse du premier, il faut utiliser des chaines courtes afin de pouvoir faire tenir l'offset négatif en moins de 3 caractères (-99 est l'offset maximum).

0x7fcf89a8a0dc - 0x7fcf89a8a090 = 76

L'adresse de accumuler est située 12 octets après le début de la zone mmapée.

Via la commande update, nous mettons à jour l'octet situé 76 octets avant avec un 0x0c.

La commande bilan va donc nous leaker l'adresse de accumuler.

  • Leaker la libc

On peut alors calculer l'adresse de la GOT afin d'y récupérer un pointeur sur la libc.

Le pointeur vers libc_start_main est situé à l'adresse d'accumuler + 0x204ed8

L'adresse du one_gadget est située à l'offset 0x464f9 de leur libc et libc_start_main à l'offset 0x21dd0.

Pour plus d'info sur le one_gadget : http://j00ru.vexillium.org/blog/24_03_15/dragons_ctf.pdf

  • shell

Pour terminer nous allons remplacer le pointeur vers la fonction select par l'adresse du one_gadget en utilisant le premier objet alloué.

0x7fcf89a8a00c: 0x00007fcf89a911c0 0x00007fcf89a914ff
0x7fcf89a8a01c: 0x00007fcf89a90995 0x00007fcf89a91387
0x7fcf89a8a02c: 0x00007fcf89a91674 0x00007fcf89a91897
0x7fcf89a8a03c: 0x00007fcf89a91860 0x0000000000000010
0x7fcf89a8a04c: 0x0000001000000000 0x00000000
0x7fcf89a8a058: 0x00007fcf89a8a06c 0x0000000000000010
0x7fcf89a8a068: 0x00000000
0x7fcf89a8a06c: 0x6161616161616161 0x6161616161616161
0x7fcf89a8a06c - 0x7fcf89a8a02c = 64

L'adresse de select est 64 octets avant l'adresse de la première chaîne de caractères.

Une fois remplacée par l'adresse du one_gadget, il suffit donc d'appeler la commande select pour obtenir un shell.

knockedup le retour

Impossible d'ouvrir le flag avec ce shell car celui ci n'est lisible que par root.

Seul knockedup tourne avec les droits root mais son fichier de config est lisible :

[balltest]
  sequence = 18547, 8846, 24467
  timeout = 2000
  command = /bin/bash -c "/sbin/iptables -I KNOCKEDUPD 1 -s %IP% -p  tcp --dport 13495 -j ACCEPT"
  type = udp

Une nouvelle séquence de port knocking nous permet d'obtenir le flag :

The flag is: 'N0w w4sn't 7h47 just 4 h34p 0f PFM?'

https://gist.github.com/agix/ffd8e5f2cb095aeba387