HackIt! 2019, Level 2

Este level parece que se le atragantó a muchos grupos. Aunque estuvimos unas cuantas horas dándole vueltas, una vez resuelto te das cuenta de que, lo que lo hacía complejo, realmente eran varios red-herring o falsas pistas. Si las seguías, estabas muerto. El level empieza con 3 ficheros: yellow, red, green. Aquí está el primer anzuelo: ¿para qué estos colores?… En fin, sacando strings, el que más llama la atención es red.

Juanan-2:2 juanan$ strings -n 12 red|more
Ktablered1000red1000^LCREATE TABLE red1000(redz blob)N
ytablered100red100
CREATE TABLE red100(reda varchar(10),redb varchar(10))H
utablered9red9
CREATE TABLE red9(reda varchar(10),redb varchar(10))H
utablered8red8
CREATE TABLE red8(reda varchar(10),redb varchar(10))H
utablered7red7
CREATE TABLE red7(reda varchar(10),redb varchar(10))H
utablered6red6
CREATE TABLE red6(reda varchar(10),redb varchar(10))H
utablered5red5
CREATE TABLE red5(reda varchar(10),redb varchar(10))H
utablered4red4
...
CREATE TABLE red1(reda varchar(10),redb varchar(10))
0000000 5473 6572 6d34 3352 000a

Vaya… una base de datos, probablemente SQLite. Y el campo redz de la tabla red1000 es de tipo blob. Estuvimos dándole vueltas y vueltas a esto. Conseguimos incluso importar la estructura de las tablas.

En la tabla red1, la columna reda tiene algo:

Pero eso ya salía en los strings, no hacía falta liarse la manta con SQLite… Mmmh, veamos qué significa:

misterio = [0x54,0x73,0x65,0x72,0x6d,0x34,0x33,0x52,0x00,0x0a]
import binascii
print("".join( chr(c) for c in misterio))
Tserm43R

¿Tserm43R? WTF? @ochoto comentó en el grupo que tal vez habría que darle la vuelta a cada par de valores (big endian?) porque los últimos bytes son un salto de línea + fin del string invertidos (0x00, 0x0a). Vamos allá (quitando el salto de línea):

misterio = [0x54,0x73,0x65,0x72,0x6d,0x34,0x33,0x52]
"".join([chr(a)+chr(b) for a,b in [i for i in zip(misterio[1::2], misterio[::2])]])

'sTre4mR3'

Tiene sentido, parece un trozo de string en h4x0r. Dejémoslo ahí y vayamos a por green. Este fue más fácil:

$ binwalk -e green

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
27337196      0x1A121EC       HPACK archive data
33554432      0x2000000       gzip compressed data, has original file name: "trololo", from Unix, last modified: 2019-07-15 23:29:50

$ ls -al _green.extracted/
total 8
drwxr-xr-x   3 juanan  wheel    96 Jul 25 21:28 .
drwxrwxrwt@ 70 root    wheel  2240 Jul 29 22:15 ..
-rw-r--r--   1 juanan  wheel     8 Jul 25 21:28 trololo

$ cat _green.extracted/trololo
ce1VEd!

Vaya, si concatenamos red con green (mismo orden que el enunciado), obtenemos ‘sTre4mR3ce1VEd!’. Tiene muy buena pinta. Sólo nos queda un fichero, yellow. Es un fichero binario, sin ningún magic number ni strings asociados. Tras muchas vueltas, se nos ocurrió algo evidente (a que sí, @navarparty? XDDD), abrirlo con Audacity:

Bingo, se oye a alguien deletreando, en inglés y a mucha velocidad, la parte que nos falta del password. Ajustando la velocidad y teniendo en cuenta que las zonas más oscuras de la señal reflejan mayúsculas, obtenemos R0tT3nB1t.

Así que… R0tT3nB1tsTre4mR3ce1VEd!

Nota: este post no refleja la dificultad de la prueba. No fue «tan fácil» como parece 🙂 Estuvimos muuuuuuuuuucho tiempo analizando los 3 binarios hasta encontrar la secuencia de pasos y herramientas adecuadas.

HackIt! 2019, level 1

Un año más, y van ya 20, asistimos a la Euskal Encounter con ganas de darlo todo, en especial al HackIt! y al CTF. En este HackIt! de la EE27 hemos sudado la gota gorda para pasar 3 pruebas de 6, logrando un segundo puesto, lo cual indica el nivel de dificultad. Eso sí, todas ellas han sido pensadas y muy curradas, por lo que lo primero, como siempre, es agradecer el trabajo de @imobilis y @marcan42. La verdad, sólo imaginar lo que costó implementar alguna de ellas (el level 3 de Minecraft en concreto pudo ser un dolor… o el 6, con la tira de leds y el LFSR en la imagen) hace que quiera invitarles a algo para que vuelvan el año que viene con nuevas ideas 🙂 En fin, entremos en harina, level1, Extreme Simplicity.

Abrimos el código fuente y vemos el siguiente trozo en JS:

function q(e){var a=">,------------------------------------------------------------------------------------[<+>[-]],----------------------------------------------------[<+>[-]],------------------------------------------------------------------------------------------------------------------[<+>[-]],----------------------------------------------------------------------------------------------------------------[<+>[-]],-------------------------------------------------[<+>[-]],--------------------------------------------------------------------------------------------------------------------[<+>[-]],-----------------------------------------------------------------------------------[<+>[-]],-------------------------------------------------------------------[<+>[-]],------------------------------------------------------------------------------------------------------------------[<+>[-]],-------------------------------------------------[<+>[-]],----------------------------------------------------------------------------------------------------------------[<+>[-]],------------------------------------------------------------------------------------[<+>[-]],[<+>[-]][-]+<[>>>++[>+++[>+++++++++++++++++++<-]<-]>>.-------------.-.<<<<[-]<[-]]>[>>>++[>+++[>+++++++++++++++++<-]<-]>>+.[>+>+<<-]>+++++++++++.>--..<----.<<<[-]]";let r=0,f=0;var i=a.length,c=new Uint8Array(3e4),s="",b=10240,k=0;for(r=0;r<i&&!(b<0);r++)switch(b--,a[r]){case">":f++;break;case"<":f>0&&f--;break;case"+":c[f]=c[f]+1&255;break;case"-":c[f]=c[f]-1&255;break;case".":s+=String.fromCharCode(c[f]);break;case",":k>=e.length?c[f]=0:c[f]=e.charCodeAt(k),k++;break;case"[":if(!c[f])for(var t=0;a[++r];)if("["===a[r])t++;else if("]"===a[r]){if(!(t>0))break;t--}break;case"]":if(c[f])for(t=0;a[--r];)if("]"===a[r])t++;else if("["===a[r]){if(!(t>0))break;t--}}return s}
$(function(){$('#password').keyup(function(e){$('#password').css({'background-color':q($('#password').val())});});});

Aquí empezamos a perder el tiempo (business as usual 🙂 debugueando con las DevTools. Creamos un nuevo snippet, pegamos el código, pulsamos en {} para un pretty-print, insertamos una última línea: console.log( q(‘password’) ), metemos un breakpoint en la línea 2 de q() y
ejecutamos la función paso a paso… Bien, así se podría resolver, pero nos llevaría unas horas… Alguien del grupo, con muy buen criterio, no sólo vió que ese código era BrainFuck, sino que pensó que traducirlo a lenguaje C era un buen primer paso. Clonamos este traductor, lo ejecutamos sobre el BrainFuck y obtenemos este sencillo programa.

Si nos fijamos, vemos el código de varios caracteres ASCII (84, 52, 114…), así que, antes de nada, probamos esa secuencia y… ¡Bingo!

file = open("bf.c", "r")
  for line in file:
    match = re.search(r'tape.*-= ([0-9]*)', line)
    if (match):
      if int(match.group(1)) > 13:
        print(chr(int(match.group(1))), end='')

Eliminar mensajes antiguos en Gmail

Apunto aquí una receta rápida para que no se me olvide y por si fuera de interés para otras personas.

El problema: uso Gmail desde hace muuuchos años y hasta hoy no me había planteado hacer limpieza. Pero, estaba al 93% de ocupación: 17.74 GB usados de 19GB disponibles, así que me he liado la manta a la cabeza y lo he podido bajar un poco, a 14.34GB = 75%, eliminando todos los mensajes de más de 10 años.

93% de utilización = sí, soy un Diógenes del correo

La solución: básicamente, usar este script en Python. Le he metido los import necesarios y lo he dejado en un Gist. Para que el script funcione, hay que activar IMAP en Gmail y (esto es importante), el acceso a aplicaciones «menos seguras» desde las opciones de seguridad de tu cuenta Google.

Activa Less secure app access.

Si tienes 2-Step Verification, tendrás que desactivarlo momentáneamente. Cuando termines de ejecutar el script, recuerda volver a activarlo (y desactiva el Less secure app access).

75% de ocupación, mucho mejor 🙂

Importar JSON en MySQL usando MySQL Shell

La utilidad MySQL Shell nos permite importar un fichero JSON en una tabla o colección de MySQL.

Primero debemos activar el protocolo mysqlX :

$ mysqlsh -u root -h localhost --mysql --dba enableXProtocol
Please provide the password for 'root@localhost':
Save password for 'root@localhost'? [Y]es/[N]o/Ne[v]er (default No):
enableXProtocol: Installing plugin mysqlx…
enableXProtocol: done

Y ahora ya podemos conectar con el servidor MySQL usando MySQLShell (y el protocolo mysqlX) :

$ mysqlsh -u root -h localhost --mysqlx

Tengo creada una base de datos llamada addi, vacía, y quiero importar ahí el fichero result.json en una colección de nombre addi_collection.

El comando a ejecutar sería :

MySQL Shell > util.importJson("result.json", {schema: "addi", collection: "addi_collection"});
Importing from file "result.json" to collection <code>addi</code>.<code>addi_collection</code> in MySQL Server at localhost:33060

El problema que tuve es que mi fichero json no tenía un campo _id único en cada registro (ver post anterior de ikasten.io), así que tuve que crearlo. Esto no sería un problema en MySQL Server > 8.0, pero estoy usando un server viejuno (5.7.19), así que obtuve este error:

Processed 182.22 KB in 80 documents in 0.0340 sec (2.35K documents/s)
Total successfully imported documents 0 (0.00 documents/s)
Document is missing a required field (MySQL Error 5115)

Tras añadir el campo _id a todos los registros, pude importar sin problemas:

util.importJson("result.json", {schema: "addi", collection: "addi_collection"});
Importing from file "result.json" to collection <code>addi</code>.<code>addi_collection</code> in MySQL Server at localhost:33060
.. 80.. 80
 Processed 182.93 KB in 80 documents in 0.0379 sec (2.11K documents/s)
 Total successfully imported documents 80 (2.11K documents/s)

Más info sobre JSON import utility en MySQL Shell.

El resultado de la importación se guarda en una colección que recuerda a las colecciones de MongoDB

Buscar y reemplazar con valores incrementales en Vim

Supongamos que tenemos un fichero JSON como el siguiente:

{ "clave1" : "valor11", "clave2": "valor12", … }
{ "clave1" : "valor21", "clave2": "valor22", … }
…
{ "clave1" : "valorN1", "clave2": "valorN2", … }

y queremos añadir un campo nuevo al comienzo, con un _id incremental, para que quede así:

{ "_id" : 1, "clave1" : "valor11", "clave2": "valor12", … }
{ "_id" : 2, "clave1" : "valor21", "clave2": "valor22", … }
…
{ "_id" : n, "clave1" : "valorN1", "clave2": "valorN2", … }

En Vim podremos hacerlo definiendo una función:

:let g:incr = 0 
:function Incr() 
:let g:incr = g:incr + 1 
:return g:incr   
:endfu

Una vez definida la función Incr(), podremos invocarla en una orden find&replace con el operador \= que permite evaluar expresiones y hacer la sustitución que buscamos:

Es decir:

:%s/^{/\="{\"_id\":" . Incr() . ","/gc

:%s/cadena_a_buscar/cadena_sustituta/gc

Cadena a buscar: ^{ (que empiece por {)
Cadena sustituta: =«{\»_id\»:» . Incr() . «,» (es decir, evaluar la expresión «_id\»:» . Incr() . «,», que inicialmente será «_id»:1 )
/gc : Cambios globales (a todo el documento, no sólo la primera aparición) y con confirmación (puedes pulsar la tecla «a» (all) cuando veas que los cambios son correctos tras las primeras sustituciones)

Si quieres más info sobre funciones y el lenguaje VimScript, échale un vistazo a este tutorial.