Solución a otro reto de la categoría 'Binary Exploitation' de la competición Sunshine CTF 2020.
En esta entrada la solución al desafío que lleva por título "speedrun05", y que, en mi opinión, presenta un nivel de dificultad medio (★★★☆☆).
- Enunciado:
- Solución: Se proporciona un archivo ejecutable (chall_05) y lo primero que hago es ejecutarlo; incluyo una cadena larga ('AAA…A'), se muestra lo que parece ser una dirección de memoria y veo que el binario es vulnerable a un desbordamiento de ‘buffer’ (en inglés, ‘buffer overflow’):
Después, compruebo los mecanismos de seguridad del binario utilizando ‘checksec’:
Se trata de un binario de 64 bits, y como se ve en la figura anterior NX está habilitado, por lo que no podré inyectar ‘shellcode’ en la pila (en inglés, ‘stack’) para ejecutarlo, y PIE está también habilitado, lo que aleatoriza la dirección base del binario para dificultar el uso por un atacante de sus funciones y de ‘gadgets’ del propio binario (en español dispositivos, y que, en este caso, son pequeños fragmentos de código ya presentes en el binario).
Decompilo el binario con ’Ghidra’:
Veo que en main() se utiliza fgets() en lugar de gets() lo que, en principio, evitaría un desbordamiento de ‘buffer’.
Sin embargo, también veo que se realiza una llamada a la función vuln():
Veo que la dirección que se muestra al ejecutar el binario es la de inicio de la función main().
Esta función
también utiliza fgets() en lugar de gets(),
pero al establecerse en 100 el número de caracteres a leer para un ‘buffer’ de 56 caracteres es susceptible a un ataque de desbordamiento de ‘buffer’.
Además,
al final de esta función se realiza una llamada a una función ubicada en la dirección que contiene la variable local_10.
Tal y como he dicho, el ‘buffer’ tiene un tamaño de 56 bytes y, conforme a los nombres de las variables local_48 y local_10 en ‘Ghidra’, la dirección de retorno de la función vuln() se encontraría desplazada 0x48 (72) bytes desde la dirección de inicio del ‘buffer’ y 0x10 (16) bytes desde la dirección de inicio de la variable local_10.
Asimismo, veo que hay una función win() a la que no se llama nunca:
En esta función se hace una llamada a system() con argumento “/bin/sh”, lo que me permitiría obtener una ‘shell’ en el servidor.
Por tanto, mi plan de ataque consiste en sobrescribir el contenido de la variable local_10 con la de inicio de la función win(), con lo que obtendré una ‘shell’, podré buscar y ver la flag, y, en consecuencia, podré resolver este reto.
Como PIE está habilitado, la dirección de la función win() se obtendrá a partir del desplazamiento de la misma con respecto a la dirección de la función main() que filtre el binario. Para obtener dicho desplazamiento utilizo ‘gdb’ y desensamblo ambas funciones:
El desplazamiento entre ambas funciones es de: 0x76d – 0x75a = 0x13 (19 bytes) y, por tanto,
la dirección de la función win() será la dirección de la función main() que filtre el binario - 19 bytes.
Para implementar el ataque
creo un pequeño ‘exploit’ mediante un ‘script’ en python para que envíe 56 bytes (0x38) de relleno, tamaño del buffer (56) o, lo que es lo mismo, 0x48 - 0x10 (72 - 16),
y luego la dirección de inicio de la función win() (a partir del desplazamiento de la misma con respecto a la dirección de la función
main() que filtre el binario, y aunque he calculado manualmente dicho desplazamiento voy a dejar que se haga automáticamente por mí en el siguiente
‘script’), con lo que sobrescribiré el contenido de la variable
local_10 con la dirección de inicio de la función
win(), se bifurcará a esa función y se abrirá una
‘shell’:
#!/usr/bin/env python3
from pwn import *
binary = context.binary = ELF('./chall_05')
p = remote('chal.2020.sunshinectf.org', 30005)
p.sendlineafter('Race, life\'s greatest.\n','')
p.recvuntil('Yes I\'m going to win: ')
main_address = p.recvline().strip()
main_address = int(main_address,16)
log.info('main: ' + hex(main_address))
win_address = main_address-(binary.sym.main-binary.sym.win)
log.info('win : ' + hex(win_address))
payload = b'A' * 56
payload += p64(win_address)
p.sendline(payload)
p.interactive()
Lo ejecuto:
A partir de que se abra la ‘shell’, lo único que queda por hacer es buscar la flag en el servidor. Veo que hay un fichero llamado flag.txt que contiene la solución a este reto: sun{chapter-four-9ca97769b74345b1}
Comentarios
Publicar un comentario