miércoles, 6 de noviembre de 2013

NcN PreQuals 2013: Reto 1

Como ya sabéis, esta pasado fin de semana fueron las finales del CTF de la NcN, organizadas por el equipo de seguridad de FaceBook, pero algunas semanas antes de eso los equipos tuvieron que pasar una clasificación basada en tres pruebas.

En la primera de ellas, nos encontrábamos ante un formulario web con un solo campo en el que supuestamente debíamos acertar con la contraseña para que nos proporcionara el Flag. En estos casos, lo primero es ver como son las conexiones a bajo nivel, para lo que normalmente lo mejor es configurar un proxy intermedio (Burp en mi caso).


Al hacerlo, nos damos cuenta que la aplicación nos devuelve un mensaje "Invalid password" antes de realizar ninguna conexión, con lo que podemos intuir que se produce algún tipo de validación en el lado cliente. Para confirmarlo, desconectamos el equipo de Internet y volvemos a probar. Es conveniente hacer esto porque puede haber plugins (flash, java, ...) que no hagan caso de la configuración del proxy del navegador, y confundirnos un poco. Una vez confirmado que hay algo en el lado cliente, solo tenemos que pegar un vistazo al código HTML:


Como podemos ver, la página carga un script llamado crypto.js, y llama a una función encrypt() al enviar el contenido del formulario. Si miramos el contenido de crypto.js veremos que tiene un javascript ofuscado que no nos deja ver muy bien la función de validación. Existen maneras de desofuscar este código, pero una rápida que suelo hacer yo es sustituir la función eval() por un alert(), de tal modo que el código javascript desofuscado que se debería ejecutar, en lugar de ser ejecutado, es mostrado en un alert, y lo podemos copiar y pegar en un fichero:


Todo lo que nos devuelve el alert lo copiamos y pegamos en un TXT, aplicamos unos cuantos saltos de linea, y podemos leer fácilmente la función encrypt() que habíamos visto antes, que tiene una pinta tal que así:


Como podemos ver, la cadena de texto que introduzcamos es convertida en un valor numérico por medio de la función numerical_value(), y a este valor se le aplican una serie de operaciones matemáticos. Si el resultado final es CERO, entonces la contraseña es correcta.

Si hacemos el proceso inverso podemos ir calculando que valor debería devolver numerical_value() para que la contraseña sea correcta:

res = res^4153      -> res debería ser 4153, para que al hacer XOR el resultado sea cero.
res = res/4             -> multiplicamos 4153 por cuatro, para hacerla operación contraria.
res = res>>>6       -> desplazamos hacia la izquierda 6 bits
res = res*(3+1+3+3+7) -> dividimos por 17.

El resultado final es 62540, aunque otros valores cercanos también pueden dar el mismo resultado, debido a las operaciones de división y desplazamiento de bits.

Ahora solo nos queda encontrar una cadena de texto que, al aplicarle numerical_value(), dé éste valor o cercano. Como podemos ver en la imagen anterior, numerical_value() coge los valores ASCII de cada uno de los caracteres y los multiplica por su posición (empezando en 1), y los suma todos para obtener el valor final.

A partir de aquí, tenemos varias opciones para obtener la cadena: hacer un análisis o probar por fuerza bruta. En este caso a mi me pareció sencillo y rápido hacerlo a mano, así que tomé esta aproximación:
  1. Modificamos el código javascript para que haga un alert() del valor devuelto por numerical_value(), así tenemos una referencia del valor de la cadena que estamos calculando (res=numerical_value(form.password.value); alert(res); ...)
  2. Vamos metiendo cadenas compuestas del caracter 'Z' y vamos aumentando la longitud hasta que el valor devuelto sea mayor que 62540 (ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ -> 63270).
  3. Ahora vamos decrementando el último caracter mientras el valor devuelto sea mayor que 62540. Cada cambio en caracteres de más a la derecha es más significativo en el resultado final, y cada cambio en caracteres de más a la izquierda proporciona un cambio más sutil.
  4. Vamos jugando con las posiciones y letras hasta que llegamos al valor deseado.
ZPZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZG -> 62547


Como podéis ver, el valor finalmente es 62547, que como decíamos no es exactamente el que habíamos calculado, pero ya sabíamos que podía pasar esto y que era perfectamente válido.

Ya tenemos uno :)

No hay comentarios :