El pasado 7 de septiembre lanzamos el Desafío ESET #37 que premiaba al primero en resolverlo con una entrada gratuita para la conferencia de seguridad ekoparty 2018.
En primer lugar, agradecemos a todos los que participaron y dedicaron parte de su tiempo intentando resolver este desafío de reversing en Linux. Aprovechamos para felicitar a Apuromafo por ser el primero en resolver el desafío de forma correcta y ganar la entrada, y también a Mariano Marino y a Víctor Meléndez, quienes también lograron resolver el desafío.
Por otra parte, si aún no tienes tu entrada para ekoparty 2018, envíanos un mensaje directo a través de nuestras cuentas de Facebook o Twitter mencionando el hashtag #WeLiveEkoparty y te indicamos cómo puedes hacer para comprar tu entrada con un 15% de descuento.
A continuación, presentamos una posible solución al desafío:
Iniciamos el análisis con IDA y observamos que la cantidad de subrutinas no es muy grande; el binario es relativamente pequeño. Vamos a la función main() y lo primero que se observa es la existencia de datos numéricos en formato hexadecimal, que se almacenan en variables locales. Hay en total 60 bytes de estos datos. Luego está el código que lee las tres contraseñas por pantalla, aparentemente tomando como máximo 43 bytes, para evitar overflows.
La primera contraseña en ser evaluada es la tercera, y se comprueba que sea numérica y de longitud 8, mediante el uso de strlen() y strspn().
Luego se llama a una subrutina que recibe input3 y otra variable como argumentos. Si entramos a esa subrutina, veremos otras secuencias de bytes hardcodeados. Buscando la primera secuencia en Google (67452301), el primer resultado es “SHA-1 Wikipedia”. En ese artículo encontramos todas las constantes y concluimos que se calcula el SHA-1 de input3.
Luego se procesan los bytes hexadecimales que habíamos visto al principio, primero para var_1A0, luego var_180 y por último var_160. Quedan entonces 3 variables de 20 bytes cada una, lo cual coincide con la longitud de un hash SHA-1.
Ese procesamiento que se hace reordena los bytes de esos hashes precalculados. Aparentemente los bytes no estaban en orden para evitar realizar una búsqueda directa de esos hashes (como se hizo con las constantes de SHA-1). Luego se utiliza la misma subrutina (que he renombrado a check_sha_1) tres veces, para comparar el hash calculado para cada input contra el valor precalculado.
Hash1 = 8af9a4abff70aaa9f2dd35446168d0a1fa6e7218
Hash2 = 2b3e371486dee4f954150c7863877108b7d4881d
Hash3 = ca6b77dc94df51f638186046788b6570b73d4fcb
Los valores de Hash1 y Hash2 pueden buscarse online y se obtiene el texto plano correspondiente:
Pass1 = supercalifragilisticoespialidoso
Pass2 = kingjames23
El tercer hash no se encuentra con una búsqueda. Si prestamos atención, ya habíamos calculado el SHA-1 para input3, y luego se calcula nuevamente, por lo que en realidad se comprueba:
Hash3 == SHA-1(SHA-1(input3))
Pero como sabemos que el tercer input debe ser numérico y de 8 dígitos, podemos obtener la solución por fuerza bruta. Si tenemos hashcat, el proceso es rápido y sencillo:
hashcat –a 3 –m 4500 ca6b77dc94df51f638186046788b6570b73d4fcb ?d?d?d?d?d?d?d?d
Y se obtiene Pass3 = 69641758
El resto del código no es necesario analizarlo, pero descifra un buffer que mostrará el flag, utilizando cifrado AES. Para el descifrado se concatenan las 3 claves ingresadas (pero en orden inverso, copiando del último al primer carácter) y el MD5 de esa larga cadena se toma como clave de descifrado. Vemos la solución a continuación: