NICO-FTP 3.0.1.19 — Buffer Overflow SEH with Bypass ASLR

Una breve descripción del binario. Bueno este programa es un cliente para el servicio de FTP el cual tiene las mismas características que cualquier otro cliente, nada de otro mundo :)

Impacto:

Se puede utilizar solo un archivo de configuración con el shellcode ya cargado para que sea ejecutado en cualquier equipo sin un usuario como intermediario que genere el payload.

Probado en:

  • Windows XP sp3 [es]
  • Windows 7 x86 [en] with ASLR

Para empezar lo primero que realiza el binario al ejecutarse es crear un archivo llamado Global.conf el cual guarda la información de cuando se ejecuto el programa por ultima vez, también crea otro archivo que es Sites.conf este archivo guarda toda la informacion y configuración de la lista de servidores.

Ahora aquí es donde se encuentra el problema, ya que podemos inyectar código en las variables del archivo de configuración (Sites.conf) para generar un crash en la aplicación, activando un desbordamiento de buffer.

archivos creados por el programa

Parámetros del archivo de configuración que son utilizados por el administrador de sitios.

parametros

Aquí podemos observar que el administrador de sitios muestra un dominio FALSO esto ocurre porque ya se encuentra cargada la configuración mas la shellcode y este simula un dominio dentro del padding para que pase mas desapercibido en el momento de la explotación.

ejecutando…

Código del exploit *.py que generar un nuevo archivo de configuración mas la shellcode que se aprovecha de un buffer overflow.

code

Archivo final que sera utilizado por el programa NicoFTP para cargar las configuraciones de conexión , ademas de accionar el BOF.

config

Después de realizar la conexión al dominio FALSO donde estaba incrustada la shellcode, esta realiza la acción de mostrar la calculadora de windows pero se podria utilizar para generar un reverse shell. (opcional)

Ahora entramos a lo mas entretenido que es Reverse sobre el binario.

Lo primero que hice fue analizar el binario para saber si tenia algún packer y me encontré con el famoso UPX :) demasiado fácil obtener el código original utilizando algún unpack o desde ollydbg, immunity de forma manual.

Bueno en esta ocasión el paso a paso del unpack no lo mostrare, pero no es complicado sacar este tipo de protección. Así que iremos directamente en busca de la función vulnerable en el binario el cual nos permite desbordar la memoria.

Info:

  • Packer: UPX v0.8x
  • Tools: RDG & Exeinfo
  • Código: Borland C++

Después de tener el código legible utilizamos DeDe que es un programa para analizar ejecutables compilados con delphi 2,3,4,5. Este no ayuda a identificar algunas cadenas de texto, direcciones a funciones como también algo del código de las vistas como (botones, textarea, etc…).

Ya obtenida esta información me dirijo a la dirección 004145E4 con el querido IDAPro. A partir de aquí encontré una función que se encarga de escribir en el archivo Site.conf que renombre como Crea_file_config y otra Show_input que se encarga de mostrar la ventana de “Edición de sitio”.

Ya revisado el flujo del programa llego por fin a las rutinas de NameChange.

Es aquí donde existe una función llamada GetText que toma los bytes del área de texto que se ingresaron como Nombre y los almacena en un buffer, también podemos observar el puntero en el stack con dirección al buffer.

También encontré que calculaba el largo de la entrada con un GetTextLen que sera utilizado como el size (0x111C que es igual a 4380 en decimal) y argumento para GetTextBuf al igual que el puntero al buffer.

Después de retorno de la función vemos como se llena el buffer con 43h en el cuadro rojo :) ahora debo saber como se están copiando estos byte en la memoria asignada (buffer).

Después de un largo rato analizando rutinas … y mas rutinas y punteros a de vtable… llegue al punto donde esta copiando la cadena del nombre al buffer, utilizando la función memcpy.

Podemos observar los argumentos enviados a la función memcpy (str_Nombre, Size_largo_nombre, pBuffer) para seguir con la ejecución de un rep movsd que lee y escribe 4 byte en el espacio asignado. Despues de la primera inserción vemos que el puntero al buffer (stack) ya vale 0x45454545…

Después de este proceso lo que hace es escribir en el archivo de configuración toda esta informacion. hasta el momento todo normal.

¿ Ahora cuando se produce el crash (overflow) ?

El crash se produce cuando se selecciona el host y damos a conectar, pero antes este debe cargar esa data para ser seleccionada así que este es el siguiente paso.

De vuelta a la acción:

Esto lo realizare paso a paso. Bueno lo primero que veo es donde se hace una llamada a los archivos de configuración y sigue así hasta obtener el path completo del archivo a leer.

Después del retorno de File_Read() y un par de funciones mas llegamos a AfterConstruction() y dentro hace una llamada a DoCreate() en el cuadro de color.

Seguimos analizando varia y varias :) funciones hasta llegar a este punto donde podemos empezar a trasear a donde va a escribir a y que dirección de buffer fue asignado. Vemos que setea una constante string_nombre con 0 para que mas adelante sea ocupada y un GetMem() con un size de 4000h esto retornara en EAX para que luego sea movida a lpReturnedString y mas argumentos que serán enviados a kernel32_GetPrivateProfileStringA().

Ahora vemos los argumentos para kernel32_GetPrivateProfileStringA(). La flecha negra es una dirección que se genera mediante GetMem() en la función ClassCreate() esta se utiliza como base para tomar el puntero al archivo de configuración después tenemos la flecha amarilla esta punta a una dirección donde esta el archivo a leer y la verde que es el puntero al buffer que sera utilizado para almacenar la strings obtenida del archivo.

Destripando la función kernel32_GetPrivateProfileStringA() vemos una llamada a kernel32_BaseDllReadWriteIniFile() donde la rutina que llena el buffer se encuentra coloreada.

Vemos que el buffer ya fue sobrescrito con 0x45.

Seguimos debugeando y dentro de _TMainForm_ConectarClick() obtenemos la rutina que ejecuta la ventana _cls_Conectar_TConnectForm para seleccionar el host a conectar :) al fin.

Entonces nos queda seleccionar el supuesto host y dar a conectar para seguir con las siguientes instrucciones.

ventana de conexión

Después entramos a la función _crash_seh y dentro nos topamos con un par de rutinas que saltan de un lado para el otro llegando a SendMessageA().

inicio del crash :)

Punteros de la función LStrFromPCharLen pisados con los bytes que he inyectado.

direcciones pisadas

Ahora que ya identifique los punteros que son pisados y ademas son los que ejecutan la excepción me enfoco en seguir traceando los flujos y condiciones hasta llegar a user32_WCSToMBEx.

Encontrando el camino

Función intermedia!

obstáculo 1

Ya dentro de la función user32_WCSToMBEx encuentro que hay una llamada a ntdll_RtlUnicodeToMultiByteN y es ahí donde esta el juego.

obstáculo 2

Esta es la gran y buscada rutina ntdll_RtlUnicodeToMultiByteN que va sobrescribiendo el Stack.

  • Rutina completa.
rutina full

La rutina lo que hace es ir tomando los bytes del cuadro amarillo e ir escribiendo en el buffer del Stack cuadro rojo en un ciclo (while) de 0x1311 en decimal es 4881 que es igual al largo del string ingresado, esto llega a pisar todo a su paso hasta los bytes finales del la estructura seh 0xFFFFFFFF y Bummm.

Preparando el ambiente entonces el size 0x1311 lo mueve a EAX mas un desplazamiento al final lo deja en EDX, el buffer a escribir en ECX y el de lectura en EAX.

contador
set de buffer

El ultimo cuadro va calculando mediante un SUB la resta del contador del While.

contador

Empieza a escribir los bytes en el buffer del Stack.

escritura en buffer

Nos detenemos a 4 bytes antes de pisar el puntero de ExceptioHandler.

stop a 4 bytes

Seguimos con esos 4 bytes y llegamos a nuestro resultado. Ahora si calculamos la dirección del puntero pisado con el inicio del buffer nos da un largo de 0x1028 bytes que se traduce en 4136 y si restamos 4 quedara en 4132 para pisar SEH jejeje… solo verificar el exploit en python y tendremos lo mismo pero aquí los valide a mas bajo nivel.

game over

Dato Opcional…

Pasamos como argumento los registros de EDX y EAX a la función Move() pero ya esta frito el Stack :) y pisado los punteros de SEH.

  • EDX=Dirección del buffer a escribir.
  • EAX=Dirección del buffer en Stack.
Copia byte en Heap

Conclusión:

La vulnerabilidad se produce porque no se valida el largo a escribir en el buffer ya que utiliza un while() que podría ser infinito eso depende del string que ingresemos como nombre y que sera utilizado como size.