Solución al reto 28: baby crackme

En el reto número 28 os proporcionábamos un crackme para poner a prueba vuestras habilidades de reversing y animaros a asistir al taller que impartirá @naivenom el próximo 8 de febrero en la h-c0n.
Como siempre vuestra respuesta ha sido genial y, en los pocos días que hemos tenido abierto el reto, hemos recibido la respuesta de tres acertantes a este reto que ya forman parte de nuestro eterno "hall of fame":

1. Mikel  
2. Backbone31
3. MRP314

Sin más no nos queda otra que agradeceros a todos los que habéis intentado resolver el reto y especialmente a los arriba listados, que además nos han remitido unos magníficos writeups. En este post os dejamos quizás el más completo, el de Mikel, aunque abajo encontraréis en PDF todos ellos.

1. Primer acercamiento e identificación del objetivo

Tras obtener el objetivo, se ve la extensión dms, la cual es una extensión extraña y de primeras hace pensar en la dmg de MacOS.


Tras una breve exploración, ese formato pertenece a la plataforma Amiga, pero se duda de que sea un programa de esa plataforma. Para identificar correctamente al objetivo, usamos el comando file, para obtener el tipo de archivo según su MagicNumber, y así focalizar mas y comprobar si la extensión corresponde:


Como vemos obtenemos que es un archivo ELF, de 64 bits, con lo que nos dispondremos a analizarlo un poco más en profundidad. Obtenemos los datos con la aplicación Readelf:


Confirmamos que es un programa ELF y de 64 bits.

2. Análisis preliminares

Para iniciar el análisis del programa, trataremos de hacer un análisis de la lógica de la aplicacion viendo el funcionamiento de este para entender la lógica que utiliza. Antes de nada, se le saca la extensión dms, dejando el ejecutable como rev, por comodidad del análisis. Tras ejecutar el programa se ve las diferencias al recibir parámetros, si no se le pasan parámetros no da resultado por la pantalla, pero si recibe uno nos muestra un banner:

- Sin parámetros:


- Con un parámetro:


- Con 2 o más parámetros:


Con ello deducimos que necesita recibir solo un parámetro para que las rutinas lo procesen.

Se testea por si abre algún puerto o demás, pero no se detecta algún tipo de interacción al respecto.
Tras inspeccionar las cadenas de texto presentes en el ejecutable por si tenemos strings interesantes:


Se observa que hay un “cartel de chico bueno” y otro de “chico malo” (ya lo vimos al introducir unos parámetros de prueba), con lo que se deduce que tenemos que validar correctamente un parámetro X para que nos muestre el “de chico bueno”. Analizando otras strings del ejecutable por si estaba la que verificaba la condición de “chico bueno”, pero no se encontró una valida.

Analizando las secciones correspondientes del ejecutable no se obtiene nada relevante para validar los parámetros.

3. Análisis estático

Con los datos obtenidos del análisis de la lógica de la aplicación, podemos comenzar con el análisis estático. En mi caso, usare el desensamblador IDA pro, por el flujo de los Basic Blocks que muestra con las funciones del programa, y mediante el server que trae el paquete para depurar el programa mas tarde en un entorno Linux. Partimos de los datos iniciales:
  • Arquitectura x64
  • Debe solo un parámetro
  • Entry point: 0x4009b0
  • Strings interesantes:
    • Carácter incorrecto buu
    • Sigue asi
    • Carácter correcto
    • Muy bien has ganado
    • Buu vas mal.
Tras el desensamblado ya vemos cosas iniciales en el proximity browser de ida:


Vemos que la función que nos interesa está en la dirección 400780. Analizamos esa función, ya que en una vista general en ella se llevan a cabo las decisiones que nos llevaran al famoso “chico bueno” y “chico malo”. Como visión general, veremos los basic blocks para detectar bucles, condicionales y demás en el código, para ello en el panel de graphic overview:


Deducimos, que tras la comprobación de inicio (cuadro marrón), hace una serie de comprobaciones intermedias, y por la estructura se aprecian bloques condicionales (cuadro azul). Tras esas comprobaciones -se aprecia un bloque que corresponde a un bucle do-while (cuadro amarillo), y tras ello un bloque final, que corresponderá con la salida de esa función.

Seccionaremos el código en las secciones dichas para facilitar su estudio.

Comprobación inicio de la función:

En esta sección analizamos el siguiente código:


Si nos fijamos, se comprueba que el valor de EDI sea 2, y como veremos más tarde corresponderá al número de parámetros pasados al programa, uno seria el “Self” del programa (la ruta completa con el nombre del programa) y otro parámetro, el que nosotros pasemos, y se ve que es un jz (jump if zero), con lo que nos indica que solo saltara a las comprobaciones intermedias si recibe solo UN parámetro.

Comprobaciones intermedias:

Los sucesivos bloques hacen una serie de comprobaciones. Antes de nada, imprime el cartel de
bienvenida y posteriormente obtiene el largo del parámetro pasado y lo almacena en RCX para
saltar al siguiente bloque:


En los bloques subsiguientes, se obtienen los caracteres según su posición almacenándolos en EBX y haciendo un XOR con unos valores fijos, en el primer bloque con 0x43h, y lo almacena en la variable con la etiqueta var_48:


En los sucesivos bloques, va cogiendo los diferentes caracteres de la cadena, hasta el noveno, y aplicando un XOR con diferentes valores y almacenándolos en las sucesivas variables.

En el siguiente bloque de código, hay como una especie de CRC, que comprueba la suma de todos los caracteres (en hexadecimal) y los compara con el valor 0x3E7h y si no es menor o igual salta a chico malo:


Comprobaciones finales:

Por último, los bloques más interesantes, los que hacen las comprobaciones pertinentes para llevarnos a “chico bueno” o “chico malo”:


La estructura pertenece a un bucle, el cual, va comprobando los diferentes valores obtenidos en las variables, con un valor, aun no sabemos cual ni como se genera, el cual lo almacena en el registro r10, y va incrementando el contador para comprobar todos los valores obtenidos e imprimiendo por pantalla los valores en formato decimal si son correctos (sino nos lleva a “chico malo”), hasta llegar el contador a nueve, en el que acto seguido, nos muestra el cartel de “chico bueno” e implementa la salida del programa.


4. Depuración del objetivo

Iniciamos la depuración, podemos decantarnos por el depurador GDB, si nos gusta algo mas grafico EDB, o ya si se prefiere Radare, en mi caso, me decanto por el depurador de IDA, ya que tengo identificadas las variables y demás. Como consideraciones iniciales pasaremos
como parámetro la cadena TESTARELPROCESO.

Iniciamos poniendo un BP en la dirección 0000000000400780 y debugamos para comprobar la condición del primer bloque que corresponde al número de parámetros pasados, siendo el valor almacenado en EDI, y estando el registro ESI apuntando a la dirección de memoria de los parámetros:


Al pasar la condición de los parámetros, imprime el cartel de bienvenida y almacena en ECX el valor 0xF que corresponde a la longitud de la cadena que pasamos como parámetro. En el siguiente bloque empieza a procesar nuestro parámetro pasado, obteniendo el primer carácter y haciendo un XOR con el valor 0x43h:


Comentadas, las instrucciones seguirían como el bloque anterior. En caso de que el contador de RCX(el largo de la cadena), fuese menor, saltaría a “chico malo”.

Tras pasar los procesos de los caracteres, nueve en total, vemos que la suma de ellos se verifica con el valor 0x3E7h, el cual, en nuestro caso, por los caracteres del parámetro pasado lo verifica como valido:


Y entramos en la parte interesante, tras pasar el PUTS() que muestra el cartel de sigue asi, vemos que entramos en el bucle que, podemos comprobar contra que verifica o testea los resultados de los XOR llevados a cabo en los bloques anteriores. En mi caso pongo un BP en la primera instrucción que corresponde al MOV en la dirección 0000000000400950, y traceamos para ver la comparación de ese bloque:


Vemos que compara el valor de la variable_78 que corresponde a 0xBh con 0x17h, que es el resultado obtenido de hacer el XOR del primer carácter de nuestro parámetro pasado con el 0x43h del primer bloque que procesa la cadena. Para ello usamos XOR Calculator y lo comprobamos:


Como se puede ver resulta 0x17h. con ello, verificamos que verifica uno por uno el resultado de esas operaciones, contra un valor almacenado en memoria, no generado dinámicamente, sino que almacenado.

Aprovechándonos de la propiedad asociativa de la operación XOR [(A xor B) xor C = A xor (B xor C)], podemos obtener los valores necesarios en la entrada para verificar el parámetro introducido y se valide para llegar a chico bueno.

Depuramos ese bloque para obtener los valores con los que se verifican los resultados de los XOR, y para ello engañaremos al programa haciendo TOGGLE VALUE en la ZERO FLAG o mejor aún, cambiamos la instrucción JNZ por JZ(corremos el riesgo de acertar en algún carácter) o sino directamente por un NOP, y así no pensamos en condicionales, con lo que hará todo el bucle, y continúe así verificando todos los caracteres(lo que sea más cómodo).


En nuestro caso, optamos por usar la instrucción NOP, y se usa un BP al terminar todo el bloque del bucle para comprobar los valores de referencia con los que se verifican los resultados de los XOR, yendo a la dirección de memoria en la que se almacenan. En nuestro caso, se acomodan los valores del Stack que se utilizan para verificar, siendo los siguientes:


El siguiente paso, es aplicar la propiedad asociativa de la operación XOR, para ello:



Para ello, lo mas cómodo para hacer operaciones de este tipo, es crear una tabla para
no perderse con los valores. La usada fue:


Y para realizar las operaciones se usó cyberchef (https://gchq.github.io/CyberChef), pero existen opciones como Cryptool, ya a elección del usuario. Para obtener nuestra cadena para validar e ir a “chico bueno”, configuramos la receta en el CyberChef, ya usando los filtros que nos den el resultado en ASCII y poder ver la cadena como tal. Para ello lo se usa la siguiente receta para ver las operaciones:

https://gchq.github.io/CyberChef/#recipe=From_Hex('Space')XOR(%7B'option':'Hex','string':'4
34330675C5228763C'%7D,'Standard',false)To_Hex('None')From_Hex_Content()From_Hex('No
ne')&input=MEIgMDAgMDAgMDkgMDMgMDAgMUIgMDAgMUQ

y se obtiene el resultado:


Para completar el cuadro en modo resumen, quedaría:


Y testamos la string pasándola como parámetro:


5. Listado writeups en pdf

Mikel
Backbone31
MRP314

2 comentarios :