Como ya comenté en un anterior post, el pasado mes de Septiembre participé como ponente en las Conferencias No cON Name en Barcelona, con una charla titulada "Debugging (Exploit) Payloads" que trataba sobre como podemos depurar lo que hace el payload/shellcode de un exploit para investigar posibles problemas.
Entre las cosas que comenté, había una slide que mostraba una posible manera de depurar un shellcode extraído con un exploit en un debugger tradicional (ollydbg, por ejemplo), y que suscitó algunas preguntas y comentarios tras la charla, así que he pensado que merecía la pena verlo con algo más de detalle. La slide en cuestión fue la siguiente:
Describiendo un poco el código, en primer lugar declaramos una variable estática llamada "shellcode" que es de tipo unsigned char, y sobre ella pegamos el contenido del shellcode que queremos depurar, extraído del exploit. A este contenido le ponemos delante y detrás un byte con valor 0xCC que en arquitectura x86 es el opcode de la instrucción "INT 3".
La interrupción 3 (INT 3) es lo que emplean los debuggers para detener la ejecución de un código en un punto (poner un breakpoint). Concretamente lo que hacen es guardarse el contenido original de la instrucción y sustituirla por el INT 3. Cuando la ejecución de ese código llega a una INT 3, esta se detiene y devuelve el control al debugger. Poniendo estos INT 3's "artificiales" en el shellcode lo que conseguimos es que la ejecución se pare en el inicio del shellcode para poder empezar a depurarlo. Ahora solo nos faltará hacer que la ejecución llegue hasta dicho shellcode, pero para eso está el código que podemos ver en el main:
int (*func)();
func = (int (*)) shellcode;
(int)(*func)();
Todos nosotros conocemos de sobra como declarar una función en C, y también como se utilizan los punteros (el puntero, el puntero a puntero, y el puntero a puntero de punteros que apuntan a punteros, que recuerdos de la universidad...). Lo que no es tan habitual es declarar un puntero a una función, al menos en una programación "habitual", pero resulta extremadamente útil para este caso.
Con la primera instrucción estamos declarando un puntero a función, es decir, estamos declarando una función pero aún no le estamos diciendo en que zona está el código de dicha función.
Con la segunda instrucción estamos asignando la dirección de la variable shellcode al puntero a la función, o lo que es lo mismo, si ahora llamamos a la función se ejecutará el contenido de la variable shellcode.
Por último, por ser un puntero a función, no se llama exactamente igual que una función normal, pero puede ser llamada como vemos en la tercera instrucción, de una forma bastante intuitiva si recordamos como se accedía al valor señalado por un puntero.
Con esto conseguimos un programa que lo único que hace es saltar la ejecución al shellcode, que hemos dejado adecuadamente preparado con un INT 3 delante para poder compilar este programa y lanzarlo con un debugger, y que se nos quede la ejecución parada al principio del shellcode, listo para depurar paso a paso o como queramos.
Una pregunta que surgió a varias personas sobre todo esto es ¿eso funciona?
La duda tiene fundamento, ya que en los sistemas modernos existen protecciones que impiden que el flujo de ejecución salte a algunas zonas de memoria, como por ejemplo la pila. Por ejemplo, en Windows, esta protección se llama DEP (Data Execution Prevention).
La respuesta era SÍ, funciona correctamente, por dos motivos:
- Las pruebas las hice con un Windows XP SP3 que, aunque ya incorpora tecnología DEP, ésta está activada en la modalidad OptIn (igual que en Windows 7, si no recuerdo mal), con lo que la protección solo se aplica a procesos de sistema y algunos servicios "suscritos" a ella, con lo que un programita hecho y compilado por nosotros no va a tener, a priori, esta protección.
- Aunque intentáramos depurar el payload en un sistema con otra opción por defecto diferente a OptIn, la variable a la que salta la ejecución (shellcode) es una variable global, y como tal se almacena en una zona de la memoria del proceso llamada rodata (read-only data), que está fuera de la pila y por lo tanto no le deberían aplicar este tipo de protecciones (aunque reconozco que no lo he probado).
¿Cómo lo hacéis vosotros? ¿ puntualización Alguna o mejora? Deja tu comentario :)