SolveIt / Level 2 / EE32 / WriteUp

Nota: Este WriteUp ha sido desarrollado por Owen, del equipo basados. ¡Muchas gracias por la colaboración! (y si alguien más quiere participar, será bienvenido 🙂

El título del segundo nivel, “Beautiful music”, nos adelanta que tendrá que ver con música.

Ya veremos si es beautiful o no

Nos proporcionan el fichero music.it, que podemos escuchar con cualquier reproductor común. Escuchamos dos instrumentos, un piano y una percusión, intercalados. Aunque esto no sea un nivel de hack-it, sino de solve-it, es inevitable ejecutar file music.it para comprobar de qué tipo de archivo se trata.

$ file music.it
music.it: Impulse Tracker module sound data - "untitled" compatible w/ITv217 created w/ITv200

Vemos que se trata de un fichero creado con Impulse Tracker, un antiguo secuenciador para MS-DOS. Antes de enfocarnos en el sonido en sí, hemos abierto el fichero con Schism Traker, para ver qué información nos da.

SchismTracker reproduciendo music.it

Este programa nos permite reproducir y editar la secuencia de las notas, su escala, etc. En el panel inferior, podemos ver qué nota se reproduce en cada paso: D#5, E-6, F#5… Nuestra intuición nos dijo que la contraseña podría estar detrás de esa secuencia de notas, pero no salía nada que tuviera sentido.

Después de trastear con SchismTracker, sin éxito, tiramos de un clásico: el espectrograma de Audacity.

Espetrograma de music.it

De aquí sí parece que puede sacarse algo. Después de darle vueltas a cómo interpretarlo, viendo el espectrograma y escuchando el audio, pensamos que el mensaje podría estar codificado en morse. En el espectrograma, se ven bloques bastante bien diferenciados, por ejemplo:

Sección del espectrograma

En este primer bloque, podemos ver que los 3 golpes de percusión y la primera nota de piano, más larga, se pueden interpretar como “…-”, lo que sería una “V”, como podemos comprobar en la tabla:

Tabla de código morse

Con cada letra que identificábamos nos convecíamos más de que íbamos por el camino correcto. Así pues, llegamos a la solución 🙂

Efectivamente, más ruido que musica

SolveIt / Level 1 / EE32

Sólo 8 equipos consiguieron superar este nivel

Vamos a hacer un paréntesis en el HackIt para hablar un poco sobre el primer nivel del SolveIt. En el equipo diariolinux solemos centrar nuestros esfuerzos únicamente en el HackIt, pero entre prueba y prueba, curioseamos el SolveIt. Pero este año, la primera prueba nos volvió locos, no entendíamos qué había que hacer.

Pero mira… bastaba con buscar en Google las dos primeras filas de ese cuadrado de letras. Y salía como primer resultado:

Vaya, ese cuadrado de letras nos suena, ¿verdad?

El sumatorio de los cubos de los números que van de 1 a n es igual al sumatorio de 1 a n al cuadrado.

Así que la solución era 243540508935111696.

Ahora que conocemos la solución al level, ¿era difícil? Sinceramente, no. Fallamos en algo básico: siempre, siempre, siempre, consultar con Google cualquier texto que aparezca en la prueba. No lo hicimos y pagamos las consecuencias.

HackIt 2024 / EE32 / Level 3 (II/II)

W3irDCrypT0, indeed.

Este level nos trajo por la calle de la amargura. Conseguimos pasar 4 niveles por encima de este pero aquí nos atascamos más allá del cierre del HackIt. No fuimos los únicos, como ya dije.

Pero quisimos solucionarlo y aprender por el camino, llegando al script final que acompaña a este post. Básicamente el servidor espera comandos cifrados (como «HELP ME PLEASE») tomando como clave la IP del cliente (suma de bytes). Así:

HELP ME PLEASE = 48 45 4c 50 20 4d 45 20 50 4c 45 41 53 45
IP CLIENTE = 207.188.163.1 = 0xcfbca301 (cada octeto a hex, concatenado)
MENSAJE = \xab\xab\xae\xb3\x81\x80uQ\xb3\xb2\xa7\xa4\xb4x

Por ejemplo, para la primera letra (‘H’), sería así:

hex(0x48 + ord(‘c’)) = ‘0xab’

Con respecto a la respuesta que envía el servidor al comando HELP ME PLEASE, podemos descifrarla de forma equivalente, pero esta vez la clave será la IP del servidor:

Mensaje cifrado: add2a451939f9d93cfc7a0a4509499a5d1d29f9a929c95a581d6a09f6a508073b4b6888082745082ada87284755c5074baa80a
IP del servidor: 172.17.0.2 (la IP de ikasten.io es 95.216.157.127, pero se está ejecutando sobre un container docker) = 0xac110002
Texto en claro: Los comandos disponibles son: PASSWORD PLEASE, BYE

De nuevo, un ejemplo: la L de ‘Los’ se obtiene así:
chr(0xad – ord(‘a’)) = ‘L’

Si enviamos ahora el comando PASSWORD PLEASE, obtendremos la clave esperada.

Addendum: habíamos llegado a la conclusión de que la clave a usar para cifrar tenía que ver con la IP del cliente y para descifrar con la IP del server. ¿Cómo? Porque compartimos un script de cifraba el «HELP ME PLEASE» y lo enviaba al server… y sólo funcionaba en uno de los ordenadores del equipo. Así que el cifrado dependía del cliente… y la pista indicaba que también del servidor. En su momento habíamos pensado en un XOR (para cifrar, funciona igual que la suma de bytes), pero no probamos la combinación ganadora: pasar los octetos de la IP a hexa, concatenar y usar esa clave como string, caracter a caracter (no como el valor hexa de 0xa =10, sino como ord(‘a’)) = 97 🤷‍♂️)

¡Ah! El script que soluciona el reto:
https://gist.github.com/juananpe/d98d4f5223c7c4b3341ca4962e1160fc

HackIt 2024 / EE32 / Level 3 (I/II)

Vamos a por uno de los levels con menor número de resoluciones de este año. Únicamente fue resuelto por insomnia, navarparty, Socram y w0pr.

El título, Can’t understand you, esta vez no parece aportar gran cosa.

La pista empieza diciendo «Si eres élite cosas interesantes descubrirás en este servidor». El puerto tcp/31337 en h4x0r es eleet (élite), luego la primera cosa que se nos ocurre es conectarnos vía netcat (podemos probarlo contra ikasten.io, he creado un docker con el backend de la prueba, ¡gracias a OntzA por compartirlo!)

El puerto tcp/31337 está respondiendo. Nos indica que introduzcamos HELP ME PLEASE

Este es el resultado de teclear HELP ME PLEASE:

Bienvenido al servidor del Hackit. Para ayuda HELP ME PLEASE
Comando:
HELP ME PLEASE
Este servidor opera con mensajes encriptados
Por ejemplo, el comando HELP ME PLEASE se queda en ������uQ�����x
Pero recuerda que el servidor usa una clave distina a la del usuario

La idea es que para comunicarnos con el servidor debemos enviarle nuestros comandos cifrados. Podemos usar el siguiente script:

import socket

def main():
    host = "ikasten.io"
    port = 31337
    command = "HELP ME PLEASE"

    # Create a socket object
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        # Connect to the server
        client_socket.connect((host, port))

        # Receive the welcome message
        welcome_msg = client_socket.recv(1024)
        print("Welcome message:", welcome_msg.decode(errors='ignore'))

        # Send the "HELP ME PLEASE" command to get the required response
        client_socket.sendall(command.encode())

        # Receive the response from the server
        response = client_socket.recv(4096)  
        print("Response from server:", response.decode(errors='ignore'))

        # Extract the encrypted command from 'se queda en' to '\n'
        start_marker = b"se queda en"
        end_marker = b"\n"
        start_idx = response.find(start_marker) + len(start_marker)
        end_idx = response.find(end_marker, start_idx)

        if start_idx != -1 and end_idx != -1:
            encrypted_command = response[start_idx:end_idx].strip()
            print("Encrypted command to use:", encrypted_command)

            # Send the encrypted command back to the server
            client_socket.sendall(encrypted_command)
            final_response = client_socket.recv(1024)
            print("Final response from server (hex):", final_response[:-1].hex())
        else:
            print("Could not find the encrypted command in the response")

    finally:
        # Close the socket connection
        client_socket.close()

if __name__ == "__main__":
    main()

El problema ahora es, ¿qué algoritmo de cifrado se está usando para enviar los comandos cifrados? ¿con qué clave? ¿qué quiere decir eso de ‘recuerda que el servidor usa una clave distina a la del usuario’? ¿el servidor responde a los comandos cifrados usando otra clave? (¡y entiendo que el mismo algoritmo!)

Todas esas preguntas nos tuvieron entretenidos horas y horas… Mañana tendremos la respuesta, pero te animo a que pruebes tus ideas ahora que dispones del mismo level montado en ikasten.io.

NOTA IMPORTANTE: el servidor se está ejecutando sobre un docker con IP 172.17.0.2.

HackIt 2024 / EE32 / Level 2

El level 2 sigue estando entre los clasificados como muy fáciles.

https://ikasten.io/ee32/hackit/level2/level2.html

Igual que en el level anterior, analizamos qué operaciones se llevan a cabo para comprobar que el password introducido es correcto (al final se compara con un string concreto: umcjqhmdohdzchm)

$(document).ready(function(){$("#password").keyup(function(o){
r=$("#password").val();function num(s){x=s+'';y=x.charCodeAt(0);if(0xD800<=y&&y<=0xDBFF){q = y;if(x.length === 1){return y;}z=x.charCodeAt(1);return ((q - 0xD800)*0x400)+(z - 0xDC00)+0x10000;}if(0xDC00<=y&&y<=0xDFFF){return y;}return y;}
d="txhupnqbgvzyjvfbadnzkmytwdauwpeeojiowkzrmrnxlxcsskbjtrifhlomldsivceyqggphcfqua";c=0;o="";for(var i=0;i<r.length;i++){c=(num(r.charAt(i))+c)%78;o+=d.charAt(c);}
$("#password").css({"background-color":o == "umcjqhmdohdzchm"?"#8f8":"#f88"});
});});

y revertimos el proceso:

def num(s):
    x = s + ''
    y = ord(x[0])
    if 0xD800 <= y <= 0xDBFF:
        q = y
        if len(x) == 1:
            return y
        z = ord(x[1])
        return ((q - 0xD800) * 0x400) + (z - 0xDC00) + 0x10000
    if 0xDC00 <= y <= 0xDFFF:
        return y
    return y

d = "txhupnqbgvzyjvfbadnzkmytwdauwpeeojiowkzrmrnxlxcsskbjtrifhlomldsivceyqggphcfqua"
target = "umcjqhmdohdzchm"
c = 0

password = []

for char in target:
    target_index = d.index(char)
    for potential_char in range(32, 127):  # ASCII printable range
        temp_c = (num(chr(potential_char)) + c) % 78
        if temp_c == target_index:
            password.append(chr(potential_char))
            c = temp_c
            break

password = ''.join(password)
print("The password is:", password)

Curiosamente, aquí pedimos que se revisara la prueba, porque obteníamos claves válidas (esta prueba mostraba el input text de la página en verde cuando introducías una clave que pasaba las validaciones correctamente) que el servidor no aceptaba al enviarla. Desde la org nos atendieron enseguida y modificaron el level para que diera por buena cualquier clave que pasara correctamente las validaciones.