Google RE Beginner 2020

Google RE Beginner 2020

Vamos a echar un vistazo a uno de los retos del ctf de google del año 2020, en concreto al reto de reversing llamado “beginner”. Para lograr el reto nos proporcionan el siguiente archivo: a.out

Hay 3 pasos esenciales cuando te enfrentas a un reto de reversing: usar file, ejecutar el archivo si es posible y usar strings. Al usar file vemos que es un ELF y que está compilado para la arquitectura x86-64, por ello obviamente tendremos que estar en un sistema basado en unix para ejecutarlo. Al ejecutarlo vemos que nos pide una flag y si metemos cualquier cadena de caracteres aleatoria nos devuelve “FAILURE” y se acaba el programa. En este caso al usar strings sobre el archivo no vamos a encontrar nada importante. Lo único interesante que podemos ver claramente es que utiliza la función strncmp, y una parte de la flag que era obvia, que es “CTF{”.


Después de probar estas tres cosas parece que vamos a tener que analizar el archivo más en profundidad. Para ello voy a utilizar la herramienta radare2, que desensambla el archivo y se puede utilizar como depurador. Ejecuto r2 -A -w -d a.out (-A para analizar el archivo nada más empezar, -w para abrir el archivo en write mode, -d para poder depurar). Una vez en radare lo primero que hago es ejecutar afl para ver que funciones hay y no veo nada que destaque, por lo que me voy a desensamblar main usando pdf @main.

pgp_key

Para tratar de entender el desensamblado de un binario siempre hay que fijarse en las zonas donde se comparen cadenas de caracteres o donde haya saltos en el programa para diferenciar entre FAILURE y SUCCESS.

En este caso, antes de llamar a la función strncmp vemos tres instrucciones poco comunes:

pshufb,paddd,pxor

Si buscamos cualquiera de estas instrucciones veremos que son instrucciones SIMD, que ejecutan una misma operacion en diferentes bloques de datos utilizando el paralelismo que permiten varios procesadores o cores para mejorar la eficiencia del programa. Pshufb hace un shuffle de los contenidos de xmm0 a partir de la máscara guardada en obj.SHUFFLE. Lo mismo pasa con las otras dos instrucciones, que son un add y un xor respectivamente, excepto porque el add se comporta algo distinto a los otros dos. El add es de 32 bits (de ahí la tercera d de doubleword) e ignora el carry. El hecho de que ignore el carry implica que se van a sumar los primeros 4 bytes de obj.ADD32 a xmm0, luego los siguientes (ignorando el posible overflow de la operación anterior) y así sucesivamente.

Desensamblado

Ahora que ya entendemos bien esas tres instrucciones importantes vamos a volver a nuestro programa para meterlas en contexto. El programa es bastante simple, primero imprime “Flag: “, luego escanea una cadena de 15 caracteres (16 si contamos el /00) guardando un puntero a esa cadena en rdi y guardando también la cadena en xmm0, para después ejecutar las tres instrucciones SIMD sobre xmm0 y cargar el resultado en var_28h, al cual apunta rsi. Una vez hecho esto llama a strncmp (de ahi el hecho de guardar las cosas en rdi y rsi, que se utilizan para pasar argumentos), para comparar la cadena inicial del usuario con el resultado de la misma tras pasarlo por las instrucciones SIMD. Si son distintos los 16 primeros caracteres (mov edx, 0x10), imprime por pantalla “FAILURE” y si son iguales hace una última comparación de la flag con obj.EXPECTED_PREFIX, que tiene la cadena “CTF{”, y si esta también se cumple imprime “SUCCESS”.

La solución más fácil para sacar la flag es ejecutar varias veces el programa metiendo los caracteres que ya sabemos la primera vez junto con caracteres random:

CTF{……….}

Y metiendo el resultado de la anterior cambiando otra vez los caracteres que tenemos claros.

Para entendender por qué funciona esto tenemos que tener en cuenta que al hacer un shuffle los caracteres de CTF{ (que sabemos que son correctos) se mueven a otra posicion y luego cambian por el add y el xor dando otro caracter correcto. De esta forma cuantos más caracteres correctos tengamos más iremos sacando, y, por lo tanto, con el simple hecho de ejecutar varias veces el programa poniendo bien los caracteres 100% correctos sacamos la flag.

Hay que tener en cuenta una cosa, que es el hecho de que al haber carry en el add dentro de cada 4 bytes y siendo los caracteres de un solo byte es posible que cambien los caracteres de cada resultado varien por un bit, por lo que lo que había dicho anteriormente de que siempre se dan nuevos caracteres correctos no es del todo cierto, pero aún así este metodo sigue funcionando porque al iterar muchas veces y sacar nuevos caracteres se van corriguiendo los carrys.

iteracion

Para sacar la flag con radare utilizamos el depurador. Voy a poner un breakpoint antes de que se ejecute el primer strncmp con el comando db, luego voy a ejecutar el programa con dc, voy a meter CTF{……….} y voy a ver el resultado mirando en var_28h con px. Luego voy a reanudar la ejecución con ood y meter el resultado anterior poniendo otra vez los caracteres CTF{} y repetir el proceso hasta que el resultado sea el mismo que el propio input.

flag

El resultado final es CTF{S1MDf0rM3!}.


Este artículo ha sido redactado con fines educativos. Cualquier acción derivada de este artículo que no tenga dicho fin entra en conflicto con nuestro propósito de hacer divulgación de contenido educativo, y es objeto de nuestra condena más enérgica.

Dbd4
comments powered by Disqus