pre { background:#eeeeee; border:1px solid #A6B0BF; font-size:120%; line-height:100%; overflow:auto; padding:10px; color:#000000 } pre:hover { border:1px solid #efefef; } code { font-size:120%; text-align:left; margin:0;padding:0; color: #000000;} .clear { clear:both; overflow:hidden; }

Bienvenido al blog

Bienvenidos al blog de seguridad en sistemas

lunes, 30 de septiembre de 2013

Reto LvL1 de la NcN2013

Siento el abandono del blog pero he tenido muchas cosas encima que no me han permitido mantenerlo como me hubiera gustado. Me vine de Boston en Agosto con el IELTS y el TOEFL calentito. Donde después de una estancia corta en España preparándolo todo, esta semana llegue a Londres para cursar el Master de Seguridad de la Información en la RHUL. 

Hace un par de días me comentaron que el CTF de la NcN iba ser este fin de semana y si nos apuntábamos para divertirnos un poco, ya se sabe, es lo que tienen los frikis, en vez de salir de fiesta nos quedamos delante de un ordenador. 

Por desgracia empezaba un viernes a las 12 de la noche por lo que me fue imposible empezar ya que el sábado tocó madrugar para ir a visitar Londres. Que por cierto, como es ya costumbre cuando me cambio de residencia, aquí tenéis la foto que tome ese sábado sobre la torre de Londres al que es hasta la fecha mi edificio favorito:


Al final empece el reto cerca pasada las 12 de la noche del sábado, reventado después del viajecito. Accedí a la web de la NcN y vi que habían tres retos. El primero era un reto Web, el segundo era un reto Java+Android con tratamiento de imágenes y el último era un ELF de un entorno en 64 bits. Como ni tenia un entorno Android virtual preparado ni un entorno con 64 bits me decante por empezar con el primer reto.

El primer reto es un simple formulario donde se nos pide una key de entrada y se nos indica si es valida o no mediante JavaScript, parece ser que la validación se está haciendo en origen. Miramos el código HTML de la página web donde al enviar el formulario se llama a la función Java Script "encrypt". Viendo la cabecera del HTML vemos que se incluye un fichero JavaScript llamado "crypt.js":
<!DOCTYPE html>
<html>
  <head>
    <title>NcN 2013 Registration Quals</title>
<link rel="stylesheet" href="../res/main.css" type="text/css" media="screen"/>
    <link href='../res/UbuntuMono.css' rel='stylesheet' type='text/css'>
    <meta content="Javier Marcos @javutin" name="author" />
<script type="text/javascript" src="crypt.js"></script>
</head>
<body>
<div id="level">
<center>
<h2 style="color: white">Discover the buried valid key:</h2>
    <form action="index.html" method="POST" onsubmit="return encrypt(this);">
    <table border=0 align="center">
     <tr>
        <td><label style="color: white" for="key"><b>Key: </b></label></td>
        <td><input type="text" name="password" id="password" class="input"></td>
<input type="hidden" name="key" id="key" value="">
<input type="hidden" name="verification" id="verification" value="yes">
     </tr>
     <tr>
        <td colspan="2" align="center"><p><input type="submit" name="send" class="button" value="Send"></p></td>
     </tr>
    </table>
    </form>
</center>
</div>
</body>
</html>
Para ello vamos a cargar ese fichero crypt y ver que contiene la función encrypt:
El Java Script mostrado está claramente ofuscado:
var _0x52ae=["\x66\x20\x6F\x28\x38\x29\x7B\x63\x20\x69\x2C\x6A\x3D\x30\x3B\x6B\x28\x69\x3D\x30\x3B\x69\x3C\x38\x2E\x6C\x3B\x69\x2B\x2B\x29\x7B\x6A\x2B\x3D\x28\x38\x5B\x69\x5D\x2E\x73\x28\x29\x2A\x28\x69\x2B\x31\x29\x29\x7D\x67\x20\x74\x2E\x75\x28\x6A\x29\x25\x76\x7D\x66\x20\x70\x28\x68\x29\x7B\x68\x3D\x68\x2E\x71\x28\x30\x29\x3B\x63\x20\x69\x3B\x6B\x28\x69\x3D\x30\x3B\x69\x3C\x77\x3B\x2B\x2B\x69\x29\x7B\x63\x20\x35\x3D\x69\x2E\x78\x28\x79\x29\x3B\x6D\x28\x35\x2E\x6C\x3D\x3D\x31\x29\x35\x3D\x22\x30\x22\x2B\x35\x3B\x35\x3D\x22\x25\x22\x2B\x35\x3B\x35\x3D\x7A\x28\x35\x29\x3B\x6D\x28\x35\x3D\x3D\x68\x29\x41\x7D\x67\x20\x69\x7D\x66\x20\x6E\x28\x38\x29\x7B\x63\x20\x69\x2C\x61\x3D\x30\x2C\x62\x3B\x6B\x28\x69\x3D\x30\x3B\x69\x3C\x38\x2E\x6C\x3B\x2B\x2B\x69\x29\x7B\x62\x3D\x70\x28\x38\x2E\x71\x28\x69\x29\x29\x3B\x61\x2B\x3D\x62\x2A\x28\x69\x2B\x31\x29\x7D\x67\x20\x61\x7D\x66\x20\x42\x28\x39\x29\x7B\x63\x20\x32\x3B\x32\x3D\x6E\x28\x39\x2E\x64\x2E\x65\x29\x3B\x32\x3D\x32\x2A\x28\x33\x2B\x31\x2B\x33\x2B\x33\x2B\x37\x29\x3B\x32\x3D\x32\x3E\x3E\x3E\x36\x3B\x32\x3D\x32\x2F\x34\x3B\x32\x3D\x32\x5E\x43\x3B\x6D\x28\x32\x21\x3D\x30\x29\x7B\x72\x28\x27\x44\x20\x64\x21\x27\x29\x7D\x45\x7B\x72\x28\x27\x46\x20\x64\x20\x3A\x29\x27\x29\x7D\x39\x2E\x47\x2E\x65\x3D\x6E\x28\x39\x2E\x64\x2E\x65\x29\x3B\x39\x2E\x48\x2E\x65\x3D\x22\x49\x22\x2B\x6F\x28\x39\x2E\x64\x2E\x65\x29\x3B\x67\x20\x4A\x7D","\x7C","\x73\x70\x6C\x69\x74","\x7C\x7C\x72\x65\x73\x7C\x7C\x7C\x68\x65\x78\x5F\x69\x7C\x7C\x7C\x73\x74\x72\x7C\x66\x6F\x72\x6D\x7C\x7C\x7C\x76\x61\x72\x7C\x70\x61\x73\x73\x77\x6F\x72\x64\x7C\x76\x61\x6C\x75\x65\x7C\x66\x75\x6E\x63\x74\x69\x6F\x6E\x7C\x72\x65\x74\x75\x72\x6E\x7C\x66\x6F\x6F\x7C\x7C\x68\x61\x73\x68\x7C\x66\x6F\x72\x7C\x6C\x65\x6E\x67\x74\x68\x7C\x69\x66\x7C\x6E\x75\x6D\x65\x72\x69\x63\x61\x6C\x5F\x76\x61\x6C\x75\x65\x7C\x73\x69\x6D\x70\x6C\x65\x48\x61\x73\x68\x7C\x61\x73\x63\x69\x69\x5F\x6F\x6E\x65\x7C\x63\x68\x61\x72\x41\x74\x7C\x61\x6C\x65\x72\x74\x7C\x63\x68\x61\x72\x43\x6F\x64\x65\x41\x74\x7C\x4D\x61\x74\x68\x7C\x61\x62\x73\x7C\x33\x31\x33\x33\x37\x7C\x32\x35\x36\x7C\x74\x6F\x53\x74\x72\x69\x6E\x67\x7C\x31\x36\x7C\x75\x6E\x65\x73\x63\x61\x70\x65\x7C\x62\x72\x65\x61\x6B\x7C\x65\x6E\x63\x72\x79\x70\x74\x7C\x34\x31\x35\x33\x7C\x49\x6E\x76\x61\x6C\x69\x64\x7C\x65\x6C\x73\x65\x7C\x43\x6F\x72\x72\x65\x63\x74\x7C\x6B\x65\x79\x7C\x76\x65\x72\x69\x66\x69\x63\x61\x74\x69\x6F\x6E\x7C\x79\x65\x73\x7C\x74\x72\x75\x65","","\x66\x72\x6F\x6D\x43\x68\x61\x72\x43\x6F\x64\x65","\x72\x65\x70\x6C\x61\x63\x65","\x5C\x77\x2B","\x5C\x62","\x67"];eval(function (_0x7038x1,_0x7038x2,_0x7038x3,_0x7038x4,_0x7038x5,_0x7038x6){_0x7038x5=function (_0x7038x3){return (_0x7038x3<_0x7038x2?_0x52ae[4]:_0x7038x5(parseInt(_0x7038x3/_0x7038x2)))+((_0x7038x3=_0x7038x3%_0x7038x2)>35?String[_0x52ae[5]](_0x7038x3+29):_0x7038x3.toString(36));} ;if(!_0x52ae[4][_0x52ae[6]](/^/,String)){while(_0x7038x3--){_0x7038x6[_0x7038x5(_0x7038x3)]=_0x7038x4[_0x7038x3]||_0x7038x5(_0x7038x3);} ;_0x7038x4=[function (_0x7038x5){return _0x7038x6[_0x7038x5];} ];_0x7038x5=function (){return _0x52ae[7];} ;_0x7038x3=1;} ;while(_0x7038x3--){if(_0x7038x4[_0x7038x3]){_0x7038x1=_0x7038x1[_0x52ae[6]]( new RegExp(_0x52ae[8]+_0x7038x5(_0x7038x3)+_0x52ae[8],_0x52ae[9]),_0x7038x4[_0x7038x3]);} ;} ;return _0x7038x1;} (_0x52ae[0],46,46,_0x52ae[3][_0x52ae[2]](_0x52ae[1]),0,{}));
Por ello, y como si de malware se tratara, uso el SpiderMonkey con los ficheros preparados que Lenny emplea en la Remmux dando como resultado el siguiente código:
function simpleHash(str){var i,hash=0;for(i=0;i<str.length;i++){hash+=(str[i].charCodeAt()*(i+1))}return Math.abs(hash)%31337}function ascii_one(foo){foo=foo.charAt(0);var i;for(i=0;i<256;++i){var hex_i=i.toString(16);if(hex_i.length==1)hex_i="0"+hex_i;hex_i="%"+hex_i;hex_i=unescape(hex_i);if(hex_i==foo)break}return i}function numerical_value(str){var i,a=0,b;for(i=0;i<str.length;++i){b=ascii_one(str.charAt(i));a+=b*(i+1)}return a}function encrypt(form){var res;res=numerical_value(form.password.value);res=res*(3+1+3+3+7);res=res>>>6;res=res/4;res=res^4153;if(res!=0){alert('Invalid password!')}else{alert('Correct password :)')}form.key.value=numerical_value(form.password.value);form.verification.value="yes"+simpleHash(form.password.value);return true}
Que en bonito es lo siguiente:
function simpleHash(str)
{
var i,hash=0;
for(i=0;i<str.length;i++)
{
hash+=(str[i].charCodeAt()*(i+1))
}
return Math.abs(hash)%31337
}


function ascii_one(foo)
{
foo=foo.charAt(0);
var i;
for(i=0;i<256;++i)
{
var hex_i=i.toString(16);
if(hex_i.length==1)hex_i="0"+hex_i;hex_i="%"+hex_i;hex_i=unescape(hex_i);
if(hex_i==foo)break
}
return i
}


function numerical_value(str)
{
var i,a=0,b;
for(i=0;i<str.length;++i)
{
b=ascii_one(str.charAt(i));
a+=b*(i+1)
}
return a
}


function encrypt(form)
{
var res;
res=numerical_value(form.password.value);
res=res*(3+1+3+3+7);
res=res>>>6;
res=res/4;
res=res^4153;
if(res!=0)
{
alert('Invalid password!')
}
else
{
alert('Correct password :)')
}
form.key.value=numerical_value(form.password.value);
form.verification.value="yes"+simpleHash(form.password.value);
return true
}
Mirando un poco vemos que la función encrypt lo que hace es pasar los caracteres de entrada a su correspondiente valor numérico ASCII teniendo en cuenta la posición que ocupa ese carácter en al cadena de entrada. 

Luego realiza una serie de operaciones y comprueba si ese valor es 0, devolviendo un campo value = "valor" y verificación es = yes"valor".  Intente modificarlo en caliente con un TamperData para que el valor value fuera vacio y verificacion=yes pero fallaba, por tanto la operación de comprobación que realiza el JS es repetido en el servidor. Por tanto el objetivo era ver como hacer que la variable res sea = 0.

Por ello lo que hay que hacer es los calculos a la inversa, sabemos que la variable tiene que valer 0 y vamos a ver que hay que hacer para obtener ese resultado, empezamos de abajo hacia arriba en los cálculos de la función encrypt:
res=res^4153;
Esto es una operación XOR por tanto si res tiene que vale 0, el resultado de la operación anterior debe valer igual al valor con el que se hace el XOR, es decir, res debe llegar a este paso valiendo 4153.
res=res/4; 
Multiplicamos 4153 por 4 = 16612
res=res>>>6;
Mover 6 bits a la derecha y rellenar el hueco por la izquierda a 0. Si tenemos 16612 en binario es 100000011100100 entonces entiendo que el numero debe valer 100000011100100XXXXXX. El valor decimal de res antes de esta operación debe ser entre 1063168 (0100000011100100000000) y 1063231 (0100000011100100111111).
 res=res*(3+1+3+3+7);
Se debe proceder a dividir los dos valores anteriores entre 17 (3+1+3+3+7). Por tanto 1063231 es 62543 mientras que 1063168 es 62540 aproximadamente. 

Por tanto la cadena de texto de la entrada una vez pasa la función numerical_value debe darnos un valor comprendido entre 62540 y 62543. 

Para esta fase instalamos el plugin firebug en firefox. Lógicamente copiando el HTML y el JS en nuestro ordenador y abriéndolo en local con el Firefox para evitar depender del servidor de la NcN. 

Una vez abierto procedemos a poner breakpoints para ir poco a poco viendo como cambia los resultados de la variable res. Para ello empezamos introduciendo cadenas de entrada para acercarse al rango pedido hasta que te quedas o te pases por muy poco.



Por ejemplo mis valores de entrada fueron los siguientes:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 64600
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 61110
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz  61900
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazz 62800
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabz 62019
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaahz 62223
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaajz 62291
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalz 62359
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaqz 62529
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaarz 62563
Teniendo en cuenta la función numerical_value que transforma los caracteres de entrada en un valor numérico:
function numerical_value(str)
{
var i,a=0,b;
for(i=0;i<str.length;++i)
{
b=ascii_one(str.charAt(i));
a+=b*(i+1)
}
return a
}
Queda claro que una vez acercado al valor buscado una forma sencilla de cuadrarlo es modificar el primer carácter puesto que es donde la multiplicación en función de la posición del string no altera el valor, es decir, es constante. De la "a" a la "b" el valor aumentara en 1, de la "a" a la "c" en 2, etc.

Cogiendo la entrada aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaqz cuyo valor es 62529, puesto que es la  más cercana al rango 62540 a 62543. Aumentamos la primera letra de la cadena hasta llegar a la "m" donde se nos queda la cadena maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaqz cuyo valor es 62541 la cual está en el rango que buscamos. 

Introducimos ese valor en el reto como valor de entrada y se nos indica que nos lo hemos pasado el primer nivel del reto.

Con esto me quedé sin hacer el segundo y tercer reto. Del segundo reto con dex2jar y jd-gui pude obtener el código fuente y con el los dos ficheros que había que revisar, los cuales parece ser que trataban con una serie de imágenes que había en el directorio RAW dentro del directorio res, se trataba de un código QR al que había que pillar cual era la posición correcta y componer el código. Respecto al tercer reto era claramente un GDB reto, con calma, una iso linux de 64 bits y buenos alimentos creo que se podría haber sacado.



Continuar...