BROP - Blind Return Oriented Programming

Apoya a HackTricks

Información Básica

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.

Ataque

1. Encontrar el desplazamiento vulnerable enviando un carácter más hasta que se detecte un mal funcionamiento del servidor

2. Fuerza bruta del canario para filtrarlo

3. Fuerza bruta de las direcciones RBP y RIP almacenadas en la pila para filtrarlas

Puedes encontrar más información sobre estos procesos aquí (BF Forked & Threaded Stack Canaries) y aquí (BF Addresses in the Stack).

4. Encontrar el gadget de parada

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.

5. Encontrar el gadget BROP

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 fue 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.

6. Encontrar PLT

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

7. Encontrar strcmp

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ída>, <dirección no leída>) -> falla

  • strcmp(<dirección no leída>, <dirección leída>) -> falla

  • strcmp(<dirección leída>, <dirección no leída>) -> falla

  • strcmp(<dirección leída>, <dirección leída>) -> no falla

Es posible verificar esto llamando a cada entrada de la tabla PLT o utilizando el camino lento del 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ída>, <dirección leída>) -> 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ída>, <dirección no leída>) -> falla

  • b'A' * offset + canary + rbp + (BROP + 0x9) + p64(0x300) + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP

  • strcmp(<dirección leída>, <dirección leída>) -> 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.

8. Encontrar Write o equivalente

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

Explotación Automática

Referencias

Apoya a HackTricks

Last updated