De 0 a exploiting (IX)

Buenas coleguirigis, y a estas alturas, exploiters de fama. Soy Fare9, y  como suelo traer, vengo con el cursillo de "De 0 a exploiting", como ya anuncié en redes sociales, estoy preparando el sistema operativo LovatOS (mi ubuntu modificado para que podáis usarlo para hacer las prácticas), aquí una imagen del arranque:
He cambiado el estilo de la letra, pero ya veis la clase de chorradillas que le voy haciendo.

Hoy los preparativos cambiarán, pues aunque esta vez será necesario volver a compilar quitando la protección de pila, veremos como saltarnos a la torera la protección ASLR, en binarios compilados sin la opción PIE (creo que es esa,si):

###############################################

gcc -g -fno-stack-protector -z execstack codigo.c -o programa

###############################################

Como dije, vamos a ver técnicas para bypasear técnicas anti exploiting, conocida como ASLR, el cual hace que los valores de la pila se muevan en cada ejecución. Por tanto ya no nos vale con saber dónde se encuentra uno de nuestros buffers o alguna función en una librería compartida.
Las técnicas que aquí usaremos, se basarán en funciones donde no importa las direcciones absolutas, si no, que aprovecharemos direcciones relativas.

Antes de poder ver estas técnicas tendremos que ver algo de teoría, muy básica. Hablaremos sobre, dónde se establecen las variables, tales como los argumentos en el programa. Los argumentos se encontrarán:

  1. Dentro de la zona de memoria reservada a los argumentos,y apuntada por estos.
  2. En buffers locales (variables locales de función)
  3. Y en los argumentos pasados a funciones
A nosotros donde nos interesa que estén, es en los argumentos pasados a las funciones, con un dibujo lo entenderemos mejor:
Bien, este dibujo nos es de sobra conocido, por encima de RET, al llamar a una función, se meten en la pila (push), los argumentos de la función de derecha a izquierda. Además eso siempre estará ahí si es el argumento de una función, con las técnicas que vamos a ver.


ret to ret

La instrucción ret, ya sabemos más o menos a estas alturas qué hace. Coge el valor al que apunta ESP (o cima de la pila) en ese momento, lo mete en EIP, y como este es el contador de programa, la CPU salta a esa instrucción.
Ahora imaginemos, que sobreescribimos EIP, con el puntero a una instrucción ret, sabemos que ESP estaría 4 bytes por encima del valor ese de EIP, y se repetiría lo de meter el valor a EIP y saltar a esa instrucción.
Pero, ¿nosotros podemos controlar el valor que ahí está después del EIP del programa?. Si es uno de los argumentos pasados al programa, entonces ya está, todo controlado.

Vamos a ver un programa, donde un programador se equivocó al añadir un pequeño numeríto:
 Ya vemos que alguien tuvo un error fatídico a la hora de establecer el valor para strncpy, pero abajo tuvo una gran idea, para detectar intento de hacking, estableció una función que checkea que no se meta ningún byte igual a 0xbf en los argumentos, así evitamos que establezca ahí el valor de un buffer.
Compilaremos como arriba dijimos. Y seguiremos los siguientes pasos para la explotación:
  1. Ver cuantos bytes es posible establecer hasta sobreescribir EIP
  2. Ver la dirección de un ret al que podamos saltar
  3. Establecer un payload que meter como argumento
  4. Explotar
Bien, intentaremos llenar el payload con un shellcode y si eso un relleno de NOPS así estamos más seguros de que la ejecución se ha llevado a cabo correctamente.  Luego el valor de EIP lo sobreescribimos con la dirección de un ret así saltaremos al argumento pasado a la función el cual es un puntero a nuestro payload, como payload pondremos una simple shell.
Ahora entonces seguiremos los pasos

1. Ver cuantos bytes es posible establecer hasta sobreescribir EIP

Para ello vamos a usar nuestro Pattern petater con por ejemplo 100 bytes:
 Y usaremos gdb para ejecutar el programa con este argumento y ver el error:
Introduciremos el valor que nos ha dado en PatternPetater, a ver si nos devuelve el máximo número de bytes que tenemos antes de sobreescribir EIP:
Como vemos el buffer acepta de máximo 52 bytes, tras esto, se sobreescribe EIP.

2. Ver la dirección de un ret al que podamos saltar

Para esta ardua tarea, usaremos objdump de la forma:

objdump -d ./nombre_programa

Vamos a ejecutarlo un par de veces, y ver cual de ellos no se ha movido de su sitio. Tras un par de veces, vemos que el ret de la función que se ejecuta no se mueve, al no haber sido compilado con la opción PIE, aunque tenemos ASLR el binario siempre se carga en la misma posición.
Aquí la dirección:
Tenemos ahí la dirección del ret: 0x080484bb (ya que son 32 bits o 4 bytes). Saltaremos a este ret después del primer ret, que realmente será lo mismo, pero esta vez haremos que retorne al argumento pasado.

3. Establecer un payload que meter como argumento

Meteremos un shellcode que sea una shell típica de AlephONE sin valores nulos '\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe3\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68'
Aquí la tenéis y podéis copiar y pegar sin problemas, luego tendremos que meter bytes hasta llegar a 52, este shellcode si lo miramos con python y la opción len veremos su longitud:
Y bueno nuestro python y nuestra inteligencia nos dirán que para llegar a 52, pues con restar nos vale. Y así tenemos que meter 14 bytes más, los cuales serán NOPs. Tras eso meteremos la dirección del ret. Quedando un payload tal que así:
 Como vemos la dirección la metemos en Little endian, el byte menos significativo primero.
Creo ya tenemos lo suficiente para el 4º paso

4. explotación

Aquí tenemos la linea de comandos a ejecutar:
Y... ¿qué pasará con esto?

Pues vamos a verlo ya de una vez:

Como vemos hemos conseguido una shell sin una dirección de un buffer en la pila, este buffer además se estaba moviendo cada dos por tres en cada ejecución así que habría sido imposible.

Pero como no me quedo muy bien, me gustaría mostrar qué está pasando a bajo nivel. Para ello vamos a ver unas imágenes con edb debugger:
Si vamos a la linea donde se hace el call de la función en la stack se verá el argumento pasado, ese argumento le hacemos click derecho y podemos poner "Follow in DUMP" así lo veremos en el dump de memoria:
Como vemos, en el DUMP de memoria del argumento, tenemos los NOPs metidos uno tras otro, la shell y la dirección de memoria.
Ahora al ejecutar hasta el ret de la función, veremos a donde apunta en la stack:
En la stack, en ESP y sobre todo edb nos lo dice, EIP está apuntando al mismo ret que se va a ejecutar, por tanto se ejecutará el ret, se volverá a ejecutar el ret y nos llevará al mundo de las camas de NOPs:

Ahaaa, ahí tenemos NOP NOP NOP NOP NOP NOP....

Así funciona a bajo nivel el ret2ret.



jmp2esp

Esta técnica es parecida a la anterior, y se suele dar en sistemas windows en las DLLs, que fueron compiladas sin seguridad, o en binarios que igual no tienen protección en ASLR, y no tienen DEP activado.
En este caso vamos a simular un programa en producción que por ahí tiene una instrucción "jmp esp". Digo que es parecida a ret2ret, ya que se basa en lo mismo, tenemos que al ejecutar el primer ret, se carga el valor en EIP y se salta a esa instrucción, ESP apunta además al primer argumento, por tanto si la instrucción que ejecutamos es un jmp esp, saltaremos al primer argumento y si además en este argumento metemos NOPS y un shellcode, viviremos felices y comeremos perdices.
Vamos a verlo en memoria a ver que tal suena la flauta:
Esto suena bien, pero ¿funcionará?


Vamos a ver nuestro programa de ejemplo:

Hemos simulado eso la instrucción del salto a ESP, además vemos un problema ahí con strcpy, tenemos un printf, que nos muestra la dirección del buffer, así que si lo ejecutamos un par de veces, vemos como cambia de dirección:

Como vemos el principio del buffer se modifica en cada ejecución, por tanto no valdrá eso de los buferiles.
Ahora lo único que haremos serán los siguientes pasos:

  1. Buscar un jmp esp en el ejecutable
  2. Ver cuantos bytes podemos meter de relleno
  3. Montar el payload (ya lo tenemos medio hecho)
  4. Explotar la vulnerabilidad

1. Buscar un jmp esp en el ejecutable

Para ello usaremos la gran herramienta Radare2 de pancake (https://github.com/radare/radare2).
Seguimos entonces estas órdenes:
 Ya tenemos la dirección del jmp esp : 0x0804846e

2. Ver cuantos bytes podemos meter de relleno
Tiraremos otra vez de nuestro querido pattern petater:
y seguido de GDB:
Ahora enchufamos ese valor en Pattern petater y a ver que sale:

Nos dice que tenemos 140 bytes para rellenar, luego sobreescribimos EIP.

3. Montar el payload
Como ya dijimos está medio hecho, metemos 140 purreles, seguido de la dirección a jmp esp, y seguido de un shellcod, metemos los NOPS que queramos, tampoco vamos a arriesgarnos:

Como vemos, tenemos el relleno de 140 bytes, la dirección de jmp esp, una cama de 10 NOPs y luego el shellcode.
Vamos a pasar a la explotación en el paso 4, a ver si tenemos suerte.

4. Explotación

Vamos entonces a juntarlo con el programa, a ver cómo queda:
 Parece incluso algo real, pero vamos a ver si funciona:

Como vemos, ha funcionado, con edb debugger se podría mirar de nuevo la ejecución:
Como vemos estamos en el ret, de la función, y si miramos la pila, tenemos la dirección del salto a esp y finalmente el shellcode.

Vemos la ejecución del jmp esp, y finalmente la del shellcode.


Hasta aquí he enseñado, técnicas de exploiting avanzadas para saltar las protecciones de ASLR. Estas técnicas a lo mejor podéis encontrarlas que tendréis que usar en CTFs o en máquinas vulnerables.

Espero que vaya gustando el curso y en el próximo POST veremos qué es un integer overflow, y un exploit remoto.

Ha sido un placer como siempre, y que usen estas técnicas en su propio beneficio...Digo... para hacer el bien y alertar a las empresas de que tienen fallos de seguridad...

Share this

Related Posts

Previous
Next Post »