sábado, 24 de septiembre de 2011

Práctico: Fundamentos de shellcoding en Windows XP SP3 en español

Muchas veces hablamos en una-al-día del "desbordamiento de memoria" y de la "inyección de código". Pocas veces explicamos en profundidad qué son estos conceptos. ¿Qué es ese código y en qué formato se "inyecta"? ¿Cómo se construye? Voy a explicar los fundamentos del shelcoding de la forma más sencilla posible.

Hace unos días buscaba shellcode probado para XP SP3 en español, y comprobé que no había demasiado en la red. Shellcode es esto:




Ese código lanza una consola. Serviría para inyectar código ante un desbordamiento de búfer. Normalmente, en las pruebas de concepto, se utiliza la calculadora o la consola (cmd.exe) para demostrar la
ejecución, mientras que "en la vida real" se utiliza shellcode más complejo que descarga un archivo e infecta la máquina, por ejemplo. En la imagen se pueden observar dos direcciones de memoria y los códigos de
los caracteres "cmd". ¿De dónde salen esos "números"? ¿Cómo sé que funciona? En realidad, es sencillo.

Probando shellcode

Para saber si un shellcode funciona, debemos lanzarlo con un programa en C. La estructura es siempre más o menos la misma, sólo cambia la variable "shellcode".




Compilarlo también es sencillo. Yo uso mingw. "gcc pruebashellcode.c –o pruebashellcode.exe". Este programa debería hacer aparecer una consola. Pero depende de las direcciones. Vamos a ver de dónde ha salido ese código.

Creando shellcode

Lo primero es crear un programa en C que haga lo que necesitamos. Por "comodidad", traduzco un poco el programa. Así veremos más fácil cómo calcular las direcciones de dos funciones.



Compilamos de nuevo, y vemos que sigue funcionando (aparece la consola). Ahora lo depuramos con gdb (se pueden usar otros). Usamos el comando "disassemble main". Comprobamos cómo se estructura el programa "a bajo nivel" en código ensamblador.



Con el comando "x/42b main+10" le pedimos al GDB que nos devuelva 42 bytes en hexadecimal a partir de main+10 (que es realmente donde comienza el programa). Y de ahí, limpiando código redundante y eliminado los caracteres "0x00" (que al usarse en una cadena en C, la acabarían y no seguiría leyendo), saldrá nuestro shellcode.



Calculando las direcciones

Mirando un poco el código, se advierten las direcciones de las funciones que nos interesan (Winexec y Exit), que son 0x00401d00 y 0x00401ca8 respectivamente. Lo que ocurre es que estas direcciones son relativas a mi programa (que se carga por defecto en 0x400000). Cuando un programa se ejecuta en Windows, se le reserva una memoria virtual (nada que ver con el "swap") para él que va desde el 0x00000000 al 0xFFFFFFFF. Luego el sistema se encarga de traducir esto a memoria "real", pero cada programa "cree" que tiene todo ese espacio para él. Los ejecutables se suelen cargar en su dirección 0x400000 por defecto, así se define en su cabecera PE. En direcciones de memoria más altas (sobre 0x7C000000) estarán las DLL de programa y sistema. Queremos por tanto direcciones absolutas para que este código funcione en cualquier Windows. Si se dejan las "relativas", el shellcode funcionará sólo en mi sistema circunstancialmente. Con las absolutas funcionará en un Windows u otro. Para calcular dónde está en Windows la dirección de la función WinExec por ejemplo, hay que saber en qué dirección se carga la DLL (la colección de funciones) que la contiene, y el "Offset" hasta esta dirección en concreto donde se encuentra la función. Con el programa "Dependency Walker), esto es muy sencillo.




El programa muestra que kernel32.dll es la que aloja la función. Kernel32.dll es común a todos los programas del sistema y siempre se carga en esa dirección 0x7C800000 en Windows SP3 en español. El Offset hasta WinExec es 0x00006250d. Sumado obtenemos 0x7C86250D. Igual para la función "Exit": 0x7C81CB12. Observamos que esas direcciones están en nuestro shellcode. Ahora el código ya es portable a cualquier Windows XP SP3. Precisamente ASLR en Vista y 7 lo que intenta es que estas direcciones de memoria sean diferentes en cada arranque de cada ordenador y por tanto, el shellcode "común" no funcione universalmente. En la práctica, lo que ha conseguido ASLR es hacer el shellcode mucho más complejo para eludir esta protección, pero todavía es posible conseguir shellcode válido.

Es habitual observar en exploits que las direcciones son modificadas por los creadores para que no funcione a la primera el código y eludir así a los principiantes que se dedican a "copiar y pegar". Sólo el que sepa
realmente cómo se crea shellcode podrá emplear el exploit donde quiera. Con este conocimiento, el shellcode que he presentado aquí podría extrapolarse a otros Windows con otras direcciones base sin problema. Pero en realidad, esto ya está "anticuado" puesto que en apenas dos años, XP dejará de tener soporte y todos los sistemas de Windows funcionarán bajo ASLR. Aun así, la explicación sirve para entender los fundamentos del shellcoding.


Sergio de los Santos
ssantos@hispasec.com
Twitter: @ssantosv


Más información:

Mingw
http://www.mingw.org/

GDB
http://www.gnu.org/software/gdb/download/

Dependency Walker
http://www.dependencywalker.com/

1 comentario:

  1. amigo gracias por la explicacion me quedo muy enclaro cosas que hasta estas altura no entendia muy bien me gustaria que me explicaras algo...
    te cuento estoy explotando un software vulnerable a BOF con la tipica funcion strcpy lo que no comprendo es como se sobreescribe la direccion de retorno asia donde debe regresar el programa si esta funcion en su prologo reserva espacio primero para un buffer destino y lo que se introduce en el stack es simplemente un puntero de 4 bytes entonces si es un puntero de 4 bytes como es posible que se sobreescriba esa direccion de retorno? si se reversa ejemplo 30 bytes en el buff lo uqe me gustaria ver es el momento en que se sobreescribe la direcion de retorno me gustaria que me aclararas esa parte.. gracias amigo muy bueno tu post

    ResponderEliminar