miércoles, 21 de mayo de 2014

Write-Up Quals Defcon 2014 - Shitsco

Este fin de semana pasado fueron las quals de la vigésimo segunda edición de la conocida conferencia de seguridad DefCon. En esta edición se vieron multitud de pruebas donde los binarios tenían mucho que ver.  En esta entrada describiré un modo de resolver la prueba “Shitsco”, la cual tenía un valor de dos puntos.


El binario descargado era en formato ELF de 32 bits, y al ser ejecutado simulaba una especie de sistema operativo, el cual dispone de una serie de comandos para ser ejecutados, tal y como muestra la siguiente imagen:

9
Realizando las primeras pruebas manuales, fue posible detectar fallos en el comando “enable” (comando utilizado para elevar privilegios dentro del sistema operativo). Tras introducir la contraseña demandada, devuelve un mensaje de error indicando que no es correcta. Posteriormente devolviendo la contraseña introducida más un número de bytes ininteligibles.

10
Aparentemente se puede tratar de una vulnerabilidad de “Memory leak”. Pasamos a debuggear el binario y así comprobar qué está pasando y qué información se está filtrando.

En la siguiente imagen es posible visualizar el código ensamblador encargado de:

- Copia en la dirección del registro EBX 32 bytes asociados a la contraseña introducida (strncpy)
- Comparar la contraseña introducida con la correcta (strcmp)
- Imprimir por pantalla la cadena asociada al registro EBX (printf)

2
Es posible visualizar como el registro “EBX” está apuntando a datos en la PILA que serán fugados hasta encontrar un byte nulo. Tras realizar una serie de pruebas se ve cómo la aplicación imprime un máximo de 32 bytes introducidos por nosotros más algunos bytes, posiblemente debido a que a la hora de reservar espacio para almacenar la contraseña no fue correctamente vaciado y rellenado con bytes nulos. Pero, ¿qué son estos bytes?

La siguiente imagen muestra diferentes bytes que son fugados tras introducir distintas cadenas en la contraseña demandada:

4
Si en el momento del “Memory leak” observamos la PILA:

5

Es posible visualizar que el byte/bytes fugados se encuentran asociados a la dirección “0xbffff418”, justo después de los 32 caracteres "A" == "\x41", y la cual se encuentra en la posición ESP+0x38. En el código mostrado anteriormente se puede observar cómo en dicha dirección se almacena el resultado de la función strcmp() que compara ambas contraseñas:

6
Por lo que, por cada contraseña añadida podremos leer el resultado de la función strcmp(). Dicha función compara byte a byte ambas cadenas y devuelve si el byte comparado es más grande o más pequeño al otro byte. De este modo podemos realizar un ataque byte a byte comparando si el byte introducido debe ser más grande o más pequeño.

Para automatizar dicha tarea se creó un script que leyera el “Memory leak” y en base al resultado, prosiga con el ataque:
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('54.72.148.186', 31337))
print s.recv(4096)

password = ""
letter = ""
found = False

for i in range(1, 32):
 for c in range(30, 126):
  found = False
  letter = chr(c)
  s.send("enable\n")
  s.recv(1024)
  s.send(password + letter + "a" * (30 - i - len(password) - len(letter)) + "\n")
  resp = s.recv(1024).encode("hex")[116:]
  print letter + " - " +resp
  if "ff" in resp:
   password += chr(c-1)
   print "Found >> " + password
   found = True
   break
   
 if found == False:
  i -= 1
  password = password[:len(password)-1] + chr(ord(password[len(password)-1:])+1)
  
s.close()

En la siguiente imagen es posible visualizar el script en funcionamiento y sacando la contraseña:

7

Finalmente se pudo obtener la contraseña que permitió elevar privilegios y conseguir el FLAG de la prueba:

8

[Otras Soluciones]

- Me ha llamado la atención la forma de solucionar el reto descrita en el siguiente enlace: "http://v0ids3curity.blogspot.in/2014/05/defcon-quals-2014-gynophage-shitsco-use.html", ya que mediante una vulnerabilidad use-after-free detectada en la función que implementa el comando "SET" puede leer el FLAG.

Espero que os haya sido de utilidad.

Saludos!!

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.