Exploiting 101
Hace algunos días tuvo lugar una nueva reunión de cbahacks, con la particularidad de que en esta edición se hicieron algunas presentaciones o mini charlas. Personalmente, intenté explicar y desarrollar un exploit en “vivo”, explotando una vulnerabilidad en el servidor de impresión NIPrint (un simple buffer overflow). Trataré de resumir aquí lo que mostré.
Para empezar, deberíamos verificar el bug, para lo cual necesitamos la versión vulnerable de NIPrint. Si leemos el advisory, nos dice que enviando más de 60 bytes al puerto TCP/515 haremos saltar el problema. Hagámoslo usando Python:
Boom! NIPrint crashea, justo como esperábamos. Próximo paso, chequear que podemos controlar EIP. Si corriéramos NIPrint dentro del debugger podríamos comprobar que muere al querer ejecutar la instrucción en la dirección 41414141 (hex para AAAA), es decir que estamos sobreescribiendo EIP con el buffer que enviamos. Lo que debemos determinar entonces es el offset en nuestro envío que nos permita “elegir” el valor de EIP.
Para determinar el offset armé unos scripts en Python: por un lado generaba un patrón que permitía determinar de forma unívoca el offset de una subcadena, y por otro corría el programa en cuestión a través de winappdbg de tal manera de que al crashear obtenía el valor de EIP, lo buscaba en el patrón generado y así averiguaba el offset (en este caso, 49) en el buffer para sobreescribir dicho registro.
La siguiente cuestión es estar seguros de que tenemos espacio para alojar nuestro shellcode. Si corremos NIPrint dentro del debugger y enviamos un buffer compuesto por 49 ‘A’, 4 ‘B’ y luego ‘C’s, deberíamos poder chequear que al momento del crash EIP vale 42424242 (las ‘B’) y la dirección apuntada por ESP (el tope del stack) contiene nuestras ‘C’ (en otro caso podría ser algún otro registro el que nos lleve a destino, o situaciones más complicadas). Ok, lo que querremos entonces será tener nuestro shellcode en lugar de las ‘C’ y lograr que EIP lleve el flujo de la ejecución a la dirección en ESP.
Ahora uno podría pensar que directamente sobreescribe EIP con la dirección en ESP y listo, pero no. Eso sería muy poco confiable, ya que esta dirección podría variar en diferentes versiones del sistema operativo, por ejemplo. Lo que habría que lograr es que el programa salte a esa dirección, es decir, encontrar una instrucción que lleve la ejecución a la dirección apuntada por ESP. El salto a ESP (jmp ESP) es una instrucción bastante común, y podemos efectuar una búsqueda directamente desde el debugger.
En general los programas en Windows tienen algunas DLLs, que si son propias del programa, suelen mapearse en memoria en direcciones constantes (ie, independiente de la versión del SO, *en general*). Sin embargo, en este caso debemos recurrir a DLLs del sistema cargadas por NIPrint (lo cual limitaría el exploit a dicha versión del SO). Por otro lado, como nuestro shellcode vendrá a continuación de haber sobreescrito EIP con esta dirección que estamos buscando, dicha dirección no debe tener “null bytes” (lo que indicaría el final del buffer!). Buscando desde el debugger (también existen “catálogos” online de direcciones en DLL por versión de SO donde encontrar el salto que buscamos), obtenemos por ejemplo (y para mi versión de Windows) que en kernel32 en 7C86467B tenemos un jmp ESP.
Qué sigue? Obtener nuestro shellcode y armar el buffer a enviar. Para generar un shellcode tenemos varias opciones, desde usar metasploit o buscar en alguna db especializada, hasta escribir el assembler a mano. Yo en particular usé un shellcode para ejecutar una calculadora. Una buena opción para empezar, es pasar un int 3 (\xCC) para hacer saltar un breakpoint en el debugger.
Y eso es más o menos la idea. A partir de los datos aquí expuestos deberían estar en condiciones de escribir el exploit.
Observaciones finales:
- El installer del NIPrint lo pueden encontrar en el archivo del grupo.
- Al momento de sobreescribir EIP, tener en cuenta que debemos invertir el orden de los bytes, ie “\x7B\x46\x86\x7C” (little endian).
- Estar seguros de qué hace el shellcode (cuidado con correr cualquier cosa que no sabemos lo que es!).
- Si pasamos \xCC como shellcode, asegurarse que los INT3 los maneja el debugger.
- Puede pasar que algunos bytes no “pasen” correctamente en el shellcode (bad chars); o bien hay que reescribir el shellcode para evitar esos bytes, o bien “encodearl” (ver metasploit por ejemplo).
- Desde Windows Vista, las DLLs podrían estar con ASLR, complicando el asunto.
- Cuando haya pulido un poco más los scripts que empecé a armar, postearé al respecto.
Comments