jueves, 26 de noviembre de 2009

Exploting (II): Stack Overflow Simple (II)

La semana pasada nos quedamos a medias de desarrollar un exploit para un código de ejemplo sencillo que era vulnerable a Buffer Overflow. Al final, habiamos obtenido que necesitamos rellenar con 60 caracteres para a continuación poder sobreescribir la dirección de retorno almacenada en la pila.

Por último, lanzabamos una pregunta ¿Por qué no funcionará si sobreescribo el EIP con 0x0022C0D0? La razón es lo que se llama "bad characters" (caracteres malos). Los caracteres malos son aquellos que pueden provocar que la transmisión de nuestro exploit hasta la memoria sea cortada por algún motivo. En este caso concreto, si intentaramos usar la dirección 0x0022C0D0 como parte de nuestro exploit sería imposible que nos funcionara, ya que contiene el caracter 0x00, que significa final de cadena, por lo que la función scanf dejará de copiar contenido en el momento que detecte este caracter, quedando nuestro exploit incompleto en la memoria, por lo que no funcionaría.

Pues mirando el OllyDbg... parece que toda la pila se encuentra en direcciones que empiezan por 0x00... ¿Cómo lo hacemos entonces?

¿Recordais que en el anterior post comentamos como algo "curioso" que el patron "CCC1CCC2..." se encontraba apuntado desde el registro ESP (puntero a la cima de la pila)? Pues bien, en lugar de saltar directamente a nuestro shellcode, vamos a dar un pequeño rodeo: vamos a saltar a alguna zona de código de nuestro proceso que contenga las intrucciones necesarias para que acabemos saltando a nuestro shellcode. Evidentemente no vamos a encontrar ninguna instrucción de salto a nuestro shellcode como tal, pero sí que es factible que encontremos instrucciones de salto a registros, por ejemplo ESP, y como en estos momentos el registro ESP apunta a nuestro shellcode...

Por poner un ejemplo, vamos a mencionar dos secuencias de instrucciones típicas que se usan para estos fines, aunque en realidad valdría cualquier tipo de secuencia, siempre y cuando la dirección en la que se encuentre no contenga ningún caracter que nos impida usarlo y que, al final, acabe saltando a nuestro shellcode de alguna manera:
  1. "jmp esp": esta es la más evidente, si sobreescribieramos el EIP con la dirección de esta instrucción, al saltar a ella inmediatamente la ejecución saltaría a la dirección contenida en el ESP, donde estaría nuestro shellcode, con lo que tendriamos resuelto el problema.
  2. "push esp" seguido de "ret": Lo usabamos en el post en el que explicabamos como modificar un exploit para que funcionara en un sistema en castellano hace algunas semanas. Utilizando esta secuencia, el registro ESP (que contiene la dirección de nuestro shellcode) será apilado en la pila. A continuación se realiza un "ret" (retorno de una función), que desapilará de la pila la dirección de retorno (funcionamiento estandar de las funciones call y ret) y saltará a esta dirección, por lo que provocaremos que salte a nuestro shellcode.
En general, podemos encontrar estas secuencias en varios lugares, pero algunos son mejores que otros. Sin duda la primera elección sería en alguna librería de la propia aplicación, ya que esta es totalmente dependiente de la versión del software, y no variará cuando se introduzcan parches del sistema operativo ni nada parecido. En este caso no va a ser posible, ya que el software vulnerable es muy muy sencillo y no tiene librerías, por lo que vamos a tener que buscar las secuencias de comandos en las librerías del sistema operativo.

Utilizar las librerías del sistema operativo no es lo mejor, ya que si por alguna de aquellas un parche aplicado al sistema operativo modifica el contenido de esta librería... la existencia de esta secuencia seguramente permanecerá, pero su posición se verá alterada por la alteración del código producido al aplicar el parche, por lo que nuestro exploit no funcionará. Si no nos queda más remedio... lo mejor sería elegir, de las opciones que tengamos, la que se encuentre en aquella librería que tradicionalmente sufra menos parcheos, aunque para este caso, por ser solo un ejemplo, hemos elegido una cualquiera.

Bueno, pues ya sabemos lo que tenemos que hacer, tenemos que buscar un "push esp; ret" (por ejemplo) ¿Cómo lo hacemos?

Lo primero de todo es saber que secuencia de opcodes son los comandos "push esp; ret" para poderlos buscar en la memoria. Para ello yo suelo emplear un truco (aunque al final acabas aprendiendote de memoria los opcodes "típicos"): Abro el Ollydbg (cualquier binario me vale) y me voy al final del todo de la zona de código, donde seguro que existe una zona sin utilizar. Una vez allí, pincho dos veces sobre el código y me sale una ventana en la que puedo modificar e intruducir la intrucción en ensamblador que quiera. Recordad desmarcar la opción "Keep size" para que aunque la instrucción que le pongamos ocupe más de 1 byte que nos la escriba igual.



Como podemos ver en la columna central, la secuencia "push esp; ret" se corresponde con los opcodes "54 C3" (en hexadecimal, claro), por lo que ya sabemos que es lo que tenemos que buscar en la memoria.

Ahora lanzamos nuestra aplicación vulnerable de nuevo con OllyDbg, volvemos a lanzar nuestro exploit y después del casque nos vamos a la zona de memoria (un botón con una M azul y buscamos en toda la memoria esa secuencia):



Le damos a buscar y nos la va a encontrar en varios sitios. Por ejemplo este:



Aquí podemos ver como la secuencia que queremos está en la dirección 0x77C21025, perteneciente a la librería "msvcrt". Lo ideal sería anotarnos los lugares en los que se encuentra para posterirmente seleccionar el que puede resultar más apropiado para el exploit.

Ya está, ¿no?, ya sabemos que si sobreescribimos 60 A's, después le metemos la dirección de retorno 0x77C21025 y posteriormente el shellcode, tendremos un exploit con el que conseguiremos ejecutar el código que queramos.

Vamos a hacer una pequeña prueba, y así de paso sabemos más o menos que tamaño tenemos para nuestro shellcode (que también es importente). Vamos a generar un shellcode que sean muchas intrucciones NOP seguidas de una instrucción INT 3 (la misma que utilizan los breakpoints de un debugger). Si todo funciona bien, cuando lancemos la aplicación con OllyDbg y lo dejemos ejecutar libremente, se producirán los siguientes pasos:
  1. Se lanza el exploit, se sobreescribe en la pila la dirección de retorno.
  2. La función vulnerable finaliza, al hacer ret, desapila la dirección de retorno 0x77C21025 (que es con la que hemos sobrescrito) y por tanto salta a la zona de código donde está el "push esp; ret" que habiamos encontrado.
  3. Se apila el ESP, que apunta a nuestro shellcode, y se realiza un ret, con lo que se vuelve a desapilar y se salta a esta dirección, con lo que se inicia la ejecución de nuestro código.
  4. Se ejecutan los 1000 NOPs que hemos introducido correctamente, y finalmente se ejecuta el INT 3 (breakpoint)
  5. La ejecución del OllyDbg se para, esta vez no por un error, sino por un INT 3 encontrado, con lo que tenemos la certeza de que nuestro exploit está funcionando correctamente.
Para generar este exploit, como hemos hecho antes, podemos usar el siguiente código en perl:

#!/usr/bin/perl
# EXPLOIT DE PRUEBAS - jselvi

# Auxiliares, para entendernos mejor...
my $nop = "\x90";
my $breakpoint = "\xcc";

# PARTES DEL EXPLOIT
my $relleno = $nop x 60;
my $eip = pack("V", 0x77C21025);

# SHELLCODE DE PRUEBA (breakpoint)
my $shellcode = $nop x 1000 . $breakpoint;

# CONSTRUIMOS EXPLOIT
my $exploit = $relleno.$eip.$shellcode;

# LANZAMOS EXPLOIT
my $file = "exploit.txt";
open( $FILE, ">$file" );
print $FILE $exploit;
close( $FILE );

print "Fichero exploit.txt creado\n";


Como nota sobre el exploit, fijaros que para introducir el EIP estamos utilizando al función pack con el parámetro "V". Esto es necesario para que transforme la dirección (en formato long) a su equivalente en little-endian para que se interprete bien.

Perfecto! Ya tenemos el exploit preparadito y funcionando, solo tenemos que cambiar el shellcode por uno que haga lo que realmente queramos. Ya estamos cada vez más cerca de esto...



Pero aún nos falta solventar un pequeño problemita más.
Lo veremos en el próximo post.

4 comentarios :

Adrián dijo...

Me acaba de surgir una duda de lo más estúpida. Usar pack u otras funciones similares no es imprescindible, ¿no?
Para que se me entienda, estas dos líneas son equivalentes:
my $eip = pack("V", 0x77C21025);
my $eip = "\x25"."\x10"."\xC2"."\x77";

¿cierto? Es más "artesanal" pero igual explica mejor el tema little endian/encoding :)

Jose Selvi dijo...

Sí, claro, es lo mismo :)

Puede que sí que sea más... autoexplicativo hacerlo como tú dices.

Gracias por el comentario! ;)

Thor dijo...

Muchas gracias por estas entradas!

Espero impaciente la siguiente jeje.

Jose Selvi dijo...

Un placer @Thor, mañana sale la última entrega ;)