Solución al reto 7 de la caja fuerte

Sin duda una de las cosas que más me gusta cuando desarrollo retos es que siempre recibimos (y aprendemos) varias formas de solucionarlos. El reto de la caja fuerte no fue una excepción y esta vez voy a intentar mostrar un recopilatorio de las distintas soluciones enviadas por todos los que habéis participado activamente.

Para comenzar vamos a diferenciar el reto en dos partes: la primera consiste en abrir virtualmente una caja fuerte y la segunda obtener la solución dentro del archivo zip obtenido.

1. Abriendo la caja fuerte

El primer desafío que nos encontramos consiste realmente en desofuscar el actionscript de un login en flash representado por una caja fuerte electrónica. En ese código encontraremos la password que necesitamos, algo que en la vida real entraría dentro de las peores prácticas..., recordar que nunca debemos añadir ninguna información sensible en un código expuesto, ¡¡¡por muy ofuscado que esté!!!

El flash podemos obtenerlo de la URL: http://sites.google.com/site/h4ckpl4y3s/hpyslogin20.swf y la escena es bastante sencilla: varios frames que incluyen una pantalla de login y un contador, una pantalla de bloqueo de sistema, otra que indica que la caja ha sido abierta y una última dedicada a los 'listillos' que intentaron avanzar a lo largo de flash en busca de la solución ;):





El código AS2 a estudiar se encuentra asociado al botón 'Proceed' e inicialmente se presenta de la siguiente forma:

on (press) {

function encode ($text:String, $key:String):String { if ($text == undefined || $key == undefined) { return "Argumentos no validos"; } var _text:String = $text; var _key:String = $key; var _encoded:String = ""; var _keyIndex:Number = 0; for (var i:Number = 0; i < _text.length; i++) { _encoded += chr (_text.charCodeAt (i) ^ _key.charCodeAt (i%_keyIndex)); _keyIndex++; if (_keyIndex == _key.length) { _keyIndex = 0; } } return _encoded; }
function decode2(src:String):String { var result:String = new String(""); for (var i:Number = 0; i<src.length; i+=2) { result += String.fromCharCode(parseInt(src.substr(i, 2), 16)); } return result; }
var _0xb345 = encode("FJEBGKEVGWD AT@ ","reto7") ; var _0xb346 = decode2(_0xb345) ; var _0xb200 = " BFA G A MGC M F DFG]" ; var _0xb404=["\x61\x64\x6D\x69\x6E\x37",_0xb346,"\x68\x74\x74\x70\x3A\x2F\x2F\x73\x69\x74\x65\x73\x2E\x67\x6F\x6F\x67\x6C\x65\x2E\x63\x6F\x6D\x2F\x73\x69\x74\x65\x2F\x68\x34\x63\x6B\x70\x6C\x34\x79\x33\x73\x2F","\x53\x5a\x69\x50\x70\x57\x64","\x5F\x62\x6C\x61\x6E\x6B"]; var _0xb203 = encode(_0xb200, "test"); var _0xb204 = _0xb404[2] + _0xb203 + ".zip"
if((username==_0xb404[0]&&password==_0xb404[1])) { gotoAndPlay(26); getURL(_0xb204,_0xb404[4]); } else { gotoAndStop(25);}

}}

Lo mejor para empezar es ir añadiendo retornos de carro y convertir el hexadecimal para ver obtener un código más legible (podemos ayudarnos de un decompilador):

on (press)
{
function encode($text, $key)
{
if ($text == undefined || $key == undefined)
{
return "Argumentos no validos";
}
var _text = $text;
var _key = $key;
var _encoded = "";
var _keyIndex = 0;
var i = 0;
while (i < _text.length)
{
_encoded = _encoded + chr(_text.charCodeAt(i) ^ _key.charCodeAt(i % _keyIndex));
++_keyIndex;
if (_keyIndex == _key.length)
{
_keyIndex = 0;
}
++i;
}
return _encoded;
}
function decode2(src)
{
var result = new String("");
var i = 0;
while (i < src.length)
{
result = result + String.fromCharCode(parseInt(src.substr(i, 2), 16));
i = i + 2;
}
return result;
}
var _0xb345 = encode("FJEBGKEVGWD AT@ ", "reto7");
var _0xb346 = decode2(_0xb345);
var _0xb200 = " BFA G A MGC M F DFG]";
var _0xb404 = ["admin7", _0xb346, "http://sites.google.com/site/h4ckpl4y3s/", "SZiPpWd", "_blank"];
var _0xb203 = encode(_0xb200, "test");
var _0xb204 = _0xb404[2] + _0xb203 + ".zip";
if (username == _0xb404[0] && password == _0xb404[1])
{
gotoAndPlay(26);
getURL(_0xb204, _0xb404[4]);
}
else
{
gotoAndStop(25);
}
}

Ahora que ya vemos un poco más claro el código, podremos observar que la primera función se trata de un simple XOR y la segunda una codificación en base8.

Después de estas dos funciones se empiezan a declarar variables. Para obtener su valor se pueden realizar las operaciones a la inversa o, aún más sencillo, modificar o depurar el código para obtener directamente su valor.

Para ello es posible replicar el código en local si tenemos instalado Macromedia Flash u otro (ej. trace (encode("48705973326b314f","reto7")) o trace (_0xb345)), o usar un compilador de AS online como wonderfl (ej. txtHello.text = _0xb346;). Podéis encontrar el código para obtener las password en http://wonderfl.net/c/mXoH, cortesía de Rubén Molina.

Al final, veremos que las variables cargadas tienen los siguientes valores:


var _0xb345 = 48705973326b314f
var _0xb346 = HpYs2k1O
var _0xb200 = BFA G A MGC M F DFG]
var _0xb404 = admin7,HpYs2k1O,http://sites.google.com/site/h4ckpl4y3s/,SZiPpWd,_blank
var _0xb203 = faff625d3d5f937fcabb9dd5ffdc0238
var _0xb204 = http://sites.google.com/site/h4ckpl4y3s/faff625d3d5f937fcabb9dd5ffdc0238.zip

Y el resto ya es pan comido: sustituímos las variables en la condición y ya tenemos el resultado:

    if (username == admin7 && password == HpYs2k1O)
{
gotoAndPlay(26);
getURL(http://sites.google.com/site/h4ckpl4y3s/faff625d3d5f937fcabb9dd5ffdc0238.zip, _blank);
}
else
{
gotoAndStop(25);
}
}

Es decir, si introducimos el usuario y la contraseña correctas, el flash nos redireccionará hacia la URL que contiene el ZIP y podremos enfrentarnos a la segunda parte del reto.

Pero antes de continuar, ¿existe alguna otra manera de obtener la URL con el ZIP? Pues sí, Rafael Pablos fue capaz de obtenerlo de una forma peculiar: haciendo pruebas con el visor de memoria de Cheat Engine llegó a formar la siguiente URL: http://sites.google.com/site/h4ckpl4y3s/admin7/-/test/-/.zip.
Si introducís esa dirección en vuestro navegador o cualquiera terminada en /.zip (ej. http://sites.google.com/site/h4ckpl4y3s/cualquiercosa/.zip) observaréis que Google Sites me jugó una mala pasada... Lo tendré en cuenta para futuros retos ;-)


Ahora sí que pasamos a la segunda parte del reto...

2. Desprotegiendo el zip

Hemos descargado el zip faff625d3d5f937fcabb9dd5ffdc0238.zip y al abrirlo veremos que existe otro fichero con el mismo nombre y que está protegido con contraseña.

Pero, ¿cuál es la contraseña?. Si nos fijamos en el nombre del zip nos daremos cuenta en seguida que se trata de una secuencia de 32 dígitos hexadecimales y que esto es típicamente un hash MD5. Lo más rápido para obtener la clave es comprobar primero en algún cracker online. Estos no son más que base de datos que contienen millones de valores MD5 y sus correspondientes equivalentes en texto plano.

Buscando en Google o en los principales sitios (podemos utilizar un script para automatizar la tarea) llegaremos a http://www.md5decrypter.co.uk/ y la contraseña 'caenigma'.

Ahora que tenemos la password, al intentar abrir el segundo comprimido con idéntico nombre faff625d3d5f937fcabb9dd5ffdc0238.zip veremos que contiene otro fichero: ¡'solucion.jpg'!. Sin embargo, al intentar abrirlo nos solicita también una segunda clave y (evidentemente) no es la misma que la anterior del MD5.

No obstante, si recordáis el análisis del código ActionScript, habréis observado en el array _0xb404 un valor que nunca fué usado, era la posición 3 que correspondía a la cadena 'SZiPpWd'. Ya está claro, se trata de la segunda clave del zip...

La utilizamos para abrir el jpg y observamos la imágen:


Si, aunque es dificilmente reconocible porque la mayoría de los mortales no hemos visto ninguno real ;), nuestra caja fuerte contenía ¡un billete de 500€!

Pero espera, que aquí no termina el reto... ¿No os parece raro que una imágen de estas dimensiones ocupe 1,52MB?
Sin duda podría tratarse de un último ejercicio de estegoanálisis: algo podría permanecer oculto...

Abrimos el jpg en un editor hexadecimal y al buscamos la cadena de fin de fichero JPG (0xFF 0xD9). Si bien es cierto se repite esta cadena varias veces la primera es la que nos importa.

A continuación copiamos lo que está desde estos 2 caracteres en adelante y lo guardamos en otro archivo, verificamos que lo que nos queda es sólo la imagen.


Observamos los caracteres del encabezado (0xFF 0xFB 0x52 0x54) del nuevo archivo y buscamos un poco en Google para identificar la secuencia, que parece tratarse de un archivo de audio, un MP3. Podemos también identificar el fichero con alguna herramienta que nos facilite el trabajo como TrID (Windows) o file (Linux).

Al reproducir el archivo escucharemos unos sonidos familiares... efectivamente se trata de código morse.

Procedemos a tratarlo mediante un programa decodificador como MRP40 o mediante un poco paciencia (tabla morse en mano) y obtenemos la siguiente frase final:

"Ni nos domaron, ni nos doblaron, ni nos van a domesticar - Marcelino Camacho, fundador CC.OO."

Agradecimientos

No quería terminar sin dar mil gracias a todos los que habéis intentado resolver el reto, con especial mención a Fernando Alonso Munevar, Ruben Molina, Rafael Pablos y a los ganadores del concurso Mauricio Gómez (Chile) y Daniel Correa (Colombia)

Actualización

Publicada fantástica resolución al reto en
sinfocol (Seguridad Informática Colombiana)

3 comentarios :

  1. Ahí va una pregunta tonta y a destiempo, pero a un servidor le cuesta tanto mantenerse al día con los feeds, como usar un editor hexadecimal: ¿cómo se busca la cadena de fin de fichero de p.ej. el jpg?

    En mi editor (ghex o jeex, en Linux) me voy a "Buscar", selecciono el tipo de valor hexadecimal e introduzco 00FF00D9, 00FF 00D9, 00 DD 00 F9... pero no hay manera de encontrar la dichosa cadena.

    ¡Bienvenido es cualquier consejo/apunte! (y disculpas por el desfase)

    ResponderEliminar
  2. Me respondo a mi mismo. Era cuestión de buscar del siguiente modo: FFD9. ¡Gracias igualmente!

    ResponderEliminar
  3. Vaya, te respondiste rápido! El prefijo hex... ;-)

    ResponderEliminar