Muy buenas a todos, una semanita más, ya sabeís soy Fare9 y aquí viene otra de "De 0 a exploiting". Tras el anterior post, aunque fuera algo largo y teórico, me quedé con ganas de más y necesitaba mostrar más cosicas.
Estas dos técnicas son: ROP y Falseo de Frame (Para esta última recomiendo entender bien bien bien esta:
De 0 a exploiting (V) )
Además como noticia, he de decir que he cambiado de sistema operativo, ahora utilizaré un Ubuntu 17.04, con la última versión de edb debugger ( le doy un Farenain point ).
Aquí os dejo con la imagen, de lo que llamo mi "LovatOS":
(que bien ha quedado lo de ubuntu ahí abajo, justo en la linea)
Antes de empezar con toda la teoría hoy como es un post largo, vamos a poner algo de musiquita al asunto para tener una lectura amena:
The prodigy - Voodoo People
The offspring - You're gonna go far
Panic at the disco - I write sins not tragedies
Paramore - Misery Business
Demi Lovato - La la land
Bien, pues como dije hoy hablaremos de ROP (Return Oriented Programming) y Falseo de Frame, estas son técnicas avanzadas de exploiting, así que os digo que podéis contactar conmigo por ejemplo a través de twitter:
Fare9 (sin problemas, más aprende un sabio de preguntas tontas...)
Antes de nada ya sabéis, igual que en el post anterior sólo desactivaremos ASLR, para ello hacéis lo siguiente:
###############################################
Desactivar la aleatoriedad en las direcciones de la pila, para ello debeis ejecutar el comando (como root):
echo 0 > /proc/sys/kernel/randomize_va_space
###### ACTUALIZACIÓN
Recompilando los ejecutables en la última
versión de Ubuntu, me activa el stack protector por defecto (conocido
como canary value), por tanto para compilar, podéis quitar las opciones
de -g -z execstack.
Podéis utilizar esto para compilar:
gcc -fno-stack-protector codigo.c -o programa ###############################################
Podéis compilar con gcc normal y corriente pues aplicaremos Return to Libc =)
Return Oriented Programming (ROP)
de partida iremos con el mismo código de el anterior post:
ya sabemos que con 254 bytes, podíamos a partir de ahí sobreescribir EIP con 4 bytes más, apuntabamos a &system, a &exit y a la cadena &/bin/sh... Bien ahora lo que haremos será encadenar funciones.
¿ Por qué se llama Return Oriented Programming ? recordemos como se establecía la memoria en el post anterior para ejecutar la función system y exit:
Ejecutabamos system y tras su ejecución teníamos que ret, era el "EIP" de system, en este caso metíamos la función exit.
Partiendo de esto, sabemos que al salir de system, ESP (cima de la pila) apunta a la función exit, se ejecuta el ret de system y EIP ahora apunta a exit y ESP a &/bin/sh (cosas de ejecutar un ret). Pues haremos que EIP apunte en lugar de exit, al siguiente código:
popl <lo que sea>
ret
¿Teóricamente esto que hará?, el popl hará que algo se meta en un registro, y ESP avance 4 bytes (pasará de &/bin/sh) y ¿qué le estará esperando tras esa cadena?, eso es un puntero a otra función, lo que llamaremos &func2, entonces se ejecutará func2 y podremos poner un exit para que se ejecute tras este func2... Todo esto es muy lioso pero vamos a hacer un dibujito y nos enteramos más:
Es muy parecido al anterior, pero bueno ahora nuestra primera función será system("/usr/bin/id"), tras esta función ESP apuntará a &/usr/bin/id y EIP apuntará a &(popl;ret), al ejecutar el popl ESP avanzará hasta &func2, la cual cuando se ejecute el ret, cargaremos en EIP &func2 y tachaaaaan se ejecutará func2 como explicabamos en el post anterior, y luego además se ejecutará exit.
Como vemos podemos ir encadenando funciones, con la única limitación de que estas funciones tengan un solo argumento, ya que popl sólo nos quita de en medio un solo argumento, necesitaríamos algo en plan (popl;popl;ret) para meter dos argumentos.
Vamos a ir ya entonces al turrón, veamos que pasos vamos a seguir:
- Obtención de la dirección de system
- Obtención de dirección (popl;ret)
- Creación de variable SHELL3=/usr/bin/id y obtención de dirección
- Obtencion de la dirección de exit
- Creación de variable SHELL2=/bin/sh
- Explotación
Obtención de la dirección de system
Para obtener el puntero a system ejecutamos el programa dentro de gdb, y tras acabar ejecutaremos dentro de la consola de gdb "p system":
Bien, ya tenemos el puntero a system:
&system = 0xb7e36060
Obtención de dirección (popl;ret)
Nos vale cualquier dirección donde existe un popl con un registro, seguido de un ret. Para ello usaremos objdump -d ./ret_to_libc y buscaremos cualquier dirección que cumpla esto:
Hemos cogido una función cualquiera, la última que salía por hacer algo el vago. Vemos al final un pop %ebx (son 4 bytes, lo que buscamos) y un ret. Tomamos la dirección del pop, y el ret se ejecutará por efecto dominó (o porque la cosa es así y EIP luego apunta a ret).
&(popl;ret) = 0x080485a6
Creación de la variable SHELL3=/usr/bin/id y obtención de dirección
Igual que vimos la otra vez, con export y luego usaremos el programa que dí en la actualización del anterior post obtendremos la dirección de memoria:
Bien, hasta aquí fácil, creamos la variable, ahora obtengamos la dirección con el programa getenv que vimos en el post anterior:
Ya tenemos otro punterito más:
&/usr/bin/id = 0xbffffac0
Obtención de la dirección de exit
pues más o menos como buscamos la dirección de system, pero escribiendo exit, no tiene más misterios:
Punterito de exit:
&exit = 0xb7e29af0
Creación de variable SHELL2=/bin/sh
Pues ya hemos visto como se crea SHELL3, ahora igual SHELL2 y obtenemos su puntero:
El último puntero que nos faltaba:
&/bin/sh = 0xbffffab1
Explotación
Ahora queda la parte que más gusta a estos Hackers, lo de exlotar:
Tenemos que montar una cadena como la que vimos en memoria:
524 'A's, puntero a system, puntero a pop;ret, puntero a /usr/bin/id, puntero a system (de nuevo), puntero a exit y finalmente puntero a /bin/sh:
Como vemos, es un exploit bastante amplio, metemos los bytes de relleno y EIP apunta directamente a system, lo que debería pasar es que se ejecute id y luego una shell, finalmente si salimos de la shell, se ejecutará exit y no dará una violación de segmento:
SIIIIIIIIIIIIIIIIII explotado, conseguimos ejecutar esta vez dos funciones y podríamos volver a ejecutar popl;ret y ejecutar más funciones, así una tras otra. Como diría mi compañero A.d.R. "vamo a echarno un bailecito para celebrarlo":
Ahora veremos la siguiente técnica, esta técnica igual está muy muy chula, y la verdad que está bien pensada, implica que tengamos bien sabido la teoría del quinto post de esta saga el abuso del frame pointer.
Falseo de frame
Si recordabamos (veis si al final lo acabo explicando), el abuso del frame pointer consistía en modificar ESP a través de la modificación del valor guardado en memoria de EBP, teníamos una función que modificabamos EBP en memoria con un valor X, al ejecutar leave;ret, cargabamos ese valor X en el registro, al volver de la función teníamos otro leave;ret, el cual ejecutaba lo siguiente el leave:
mov %ebp, %esp # cargamos en esp el valor de ebp para limpiar el frame pointer
popl %ebp # metemos el top de la pila en %ebp y sumamos 4 a ESP
Como vemos, a ESP se le suma 4 al hacer el popl, luego al ejecutar un ret, en EIP se carga lo que hay en ESP y se salta a esa dirección. Aprovechabamos a meter en el buffer un payload y cargabamos en EBP el puntero al payload - 4, así cuando llegara a EIP era el puntero al payload directamente.
Bien esto era al tener la posibilidad de ejecutar código en la pila, ahora usaremos nuestros conocimientos en return to libc, para crear otro payload que no se ejecute en la pila, pero fijate que apañaditos somos, además en EIP (como ahora si podemos acceder), para no tener que esperar un leave;ret de otra función, meteremos un puntero a leave;ret, así tras ejecutar el leave;ret y cargar una dirección X en EBP, ejecutamos de nuevo leave;ret y cargamos en ESP (y por ende EIP) X + 4.
Fijate que con pocas cosillas nos montamos un rico exploit, esto me recuerda a alguien:
A quién me recordará
Vamos a ver una imagen de como quedaría en memoria esto, así lo entenderemos mejor:
En donde se guarda EBP meteremos &buffer, y en donde se guarda EIP meteremos &(leave;ret), así cuando se ejecute este segundo leave;ret, saltaremos a la dirección del buffer + 4, por eso al principio del buffer metemos 4 'A's, seguidamente ejecutamos un system(/bin/sh) como hacíamos anteriormente, y sabemos que después se ejecutaría el &leave;ret que viene después de system, vamos a probar a ejecutar.
La dirección &system, y &/bin/sh, las tenemos del ejemplo anterior, entonces seguiremos los siguientes pasos:
- Obtención de la dirección de un leave;ret
- Obtención de la dirección del buffer
- Explotación
Obtención de la dirección de un leave;ret
Para esta tarea usaremos nuestro amigo objdump, el cual nos dará direcciones y opcodes, además de las instrucciones ensamblador:
Como, vemos y para no irnos mu lejos, cogemos la dirección del leave;ret directamente de la función vulnerable. Por tanto lo añadimos a la lista:
&(leave;ret) = 0x080484a3
Obtención de la dirección del buffer
Este trabajo nos llevará un poquito más (no todo el monte es oregano), usaremos edb debugger para saber dónde carajos strcpy guarda nuestra preciada cadena.
Para ello ejecutaremos edb con todos los datos que tenemos menos con la dirección del buffer, que es lo único que nos queda:
"Fare9 eres un egocéntrico", Últimas reflexiones de Nietzsche...
En lugar de la dirección del buffer, hemos puesto otras cuatro 'AAAA'.
Bueno, vamos a ver que pasa si ejecutamos esto:
Tenemos aquí, un montón de código, pero edb no nos carga directamente nuestro Main, según EIP estamos en ld, y ld es el enlazador... Bueno da igual, pulsamos F9 y vamos al main:
Esto es ya el main, el nuevo edb debugger además, nos indica perfectamente que estamos en el main de ret_to_libc en el desplazamiento 0, ya os digo que las mejoras son realmente buenas.
Vamos a ir ejecutando con F7, hasta llegar a nuestra función fvuln, como hemos metido el número correcto de argumentos no tendremos problemas:
Aquí tenemos la última frontera , en la pila vemos que los argumentos pasados son los que hemos metido en la shell. Pues igual F7, y llegaremos al call de strcpy, ahí entonces veremos los argumentos pasados a esta función de copia de cadenas, el primer argumento será nuestro puntero a buffer:
Como vemos, en la pila, el último argumento que se mete (recordemos se meten de derecha a izquierda), es el primer argumento de strcpy. Por tanto ese será el puntero al buffer:
&buffer = 0xbfffec60
Explotación
Ahora tendremos que montar la cadena antes vista, ya sabemos, 4 'A's , puntero a system, puntero a leave;ret, puntero a /bin/sh, muchas 'A's, puntero a buffer y puntero a leave;ret:
Y veamos que pasa en su ejecución:
Otra vez lo hemos vuelto a hacer, hemos explotado un programa con otra nueva técnica, ahora que vemos que la ejecución se completó vamos a darle a exit y nos vamos al carajo:
Vaya, vaya, parece que nos hemos ido al carajo de verdad...
Ha habido un problema, tras la ejecución del leave;ret, que había después del system, dentro del cual podríamos haber puesto un exit, pero quería enlazar con lo siguiente. Dejamos el leave;ret para seguir la ejecución, pero ¿qué se ha cargó en EBP y luego en ESP y EIP?.
Si recordamos el primer leave;ret cargó en EBP la dirección del buffer, el segundo cargó en ESP la dirección del buffer + 4 y se metió en EIP la dirección de buffer+4, pero en EBP metimos el valor al que apuntaba la dirección del buffer, en este caso AAAA, entonces al ejecutar el tercer leave;ret, se cargó en ESP 0x41414141, y se intentó hacer un popl y un ret de eso, con lo cual son direcciones de memoria extrañas para nuestro programa al no estar direccionada.
Por tanto vemos que el problema fue que se intentó saltar a AAAA, ¿cómo podemos solucionarlo?, pues podemos meter una segunda función en un segundo "buffer", entonces tendremos que se saltará a una segunda función y otra vez tendremos un encadenamiento de funciones. Lo veremos mejor con uno de mis dibujitos:
Como podemos ver, he dibujado la memoria, vemos que tenemos el gran buffer arriba separado en dos buffers, seguido de EBP y EIP en memoria. Vemos que la primera ejecución nos llevaría al primer system, y la segunda saltaría las 4 'A's del buffer2 e iríamos directos al segundo system, el cual se encargaría de parar la ejecución con un exit.
La dirección del segundo buffer es sencilla de calcular, si el primer buffer estaba en 0xbfffec60 contamos, hasta &system +4, hasta &(leave;ret) + 4, hasta &/usr/bin/id +4, y para finalizar hasta el principio del segundo buffer AAAA +4. En total tenemos que sumar 16, que como bien sabeis en hexadecimal es 0x10.
Si sumamos, integramos, y metemos un módulo por aquí, nos da que el segundo buffer comienza en 0xbfffec70.
Vamos entonces a la explotación:
Este es el exploit, como vemos se ha compliccado algo, al final añadimos 488 'A's ya que máximo se podían meter 524 hasta llegar a EIP, así que si vamos restando 4 y 4 y 4 ... de las direcciones de memoria metidas (sin contar la última que es la de EIP) es el número de 'A's que necesitamos (en el exploit anterior pasaba lo mismo).
Veamos su ejecución:
Como vemos la primera ejecución es de system("/usr/bin/id") y seguido nos encontramos con nuestra rica shell, hemos encadenado funciones, y podríamos encadenar más mientras nos diera el buffer con la misma técnica. Finalmente tras exit, se ejecuta la función exit (valga la redundancia) evitando cualquier violación de segmento.
--------------------------------------------------------FIN
Hoy hemos aprendido cosas interesantes, dos técnicas que son usadas para poder movernos por la pila con libertad, además de poder saltar algunas medidas de seguridad como la ejecución de código en la pila.
Espero estén gustando los posts y arriba tienen mi twitter para cualquier clase de duda, si les gustó compartan y así más gente podrá ir aprendiendo paso a paso.
Hasta el próximo post, Fare9.