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.

HackIt 2024 / EE32 / Level 1

Briefing del HackIt/SolveIt 2024

Un año más (y van unos cuantos, desde la Euskal VII) hemos vuelto a participar en el HackIt. Este año venía con novedades importantes: Marcan cedía el testigo a OntzA (Xabier Eizmendi, a la izquierda de la foto) de NavarParty, que junto a Imobilis (Juan Traverso, derecha), han organizado una excelente edición.

Había más retos y más diversidad en las pruebas, desde los muy fáciles a alguno muy complicado (sólo superado por w0pr, as usual 🙂

Al lío. Esta vez me encargué de hacer una copia de los retos, tanto del hackit como del solveit. Están publicados en Ikasten.io, siguiendo este esquema de URL:

HackIt:

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

SolveIt:

https://ikasten.io/ee32/solveit/level1/level1.html

El primer level del HackIt, como suele ser habitual ha sido muy sencillo. No siempre es así, por ejemplo, creo que fue el año pasado, en una prueba de WebGL, fue una excepción.

En el código fuente vemos una simple operación de XOR

$(document).ready(function(){$("#password").
keyup(function(o){
r=$("#password").val();t="youknowit";c=[0x3c,0xe,0x6,0x12,0x1e,0x5c,0x16,0x1a,0xd];

e="";for(i=0;i<r.length;i++){
e+=String.fromCharCode(r.charCodeAt(i)^c[i%9]);}
$("#password").css({"background-color":e==t?

"#8f8":"#f88"});
});});

Reversible muy fácilmente:

def reverse_algorithm(encrypted_str, c):
password = ""
for i in range(len(encrypted_str)):
char = chr(ord(encrypted_str[i]) ^ c[i % len(c)])
password += char
return password

encrypted_str = "youknowit"
c = [0x3c, 0x0e, 0x06, 0x12, 0x1e, 0x5c, 0x16, 0x1a, 0x0d]

password = reverse_algorithm(encrypted_str, c)
print("The password is:", password)

Easy peasy!

#GeratuEtxean: HackIt! Level 3

Para terminar, un clásico.

Un ELF para arm64 con mensaje para w0pr / Ramandi incluido 🙂

Abrimos con Ghidra y vemos que hay una función encargada de pedirnos 16 caracteres y comprobar que forman una key correcta.

Toca generar el programa que revierta las comprobaciones… Pero antes habrá que arreglar ese monstruo de código del descompilador…

$ file cambridge_technology
cambridge_technology: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, stripped

$ uname -a
Linux ip.ec2.internal 5.0.10-300.fc30.aarch64 #1 SMP Tue Apr 30 16:06:13 UTC 2019 aarch64 aarch64 aarch64 GNU/Linux

$ ./cambridge_technology
Password: 12312312313123131
FAIL!

w0pr (@abeaumont en concreto) fue el primer y único equipo que consiguió hacer ingeniería inversa de ese monstruo ¯\_(ツ)_/¯ Alfredo ha publicado el write-up, merece la pena leerlo (y replicarlo) con detalle.

#GeratuEtxean: HackIt! Level 2

Nuestros espias han localizado la flag de este nivel, pero… ¡Alguien ha destruido la caja de password! Afortunadamente, nos han proporcionado instrucciones para enviarla, pero no acabamos de entenderlas… ¿Nos ayudas?

— BEGIN TRANSMISSION —
PAYLOAD FOUND ON trololo@54.171.128.20:34342
ACCESS b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACD48tA2UHkNwf1gjzFoefbSiiA3s0+FIYWYOlxHuwDAVAAAALhSf19rUn9f awAAAAtzc2gtZWQyNTUxOQAAACD48tA2UHkNwf1gjzFoefbSiiA3s0+FIYWYOlxHuwDAVA AAAECeukBbUT2Vlozfd98BRRvKGCFRc0mdvRhAItlDfp1U7vjy0DZQeQ3B/WCPMWh59tKK IDezT4UhhZg6XEe7AMBUAAAALnJvb3RAaXAtMTcyLTMxLTYtNjUuZXUtd2VzdC0xLmNvbX B1dGUuaW50ZXJuYWwBAgMEBQYH
IDENTIFY USING 20ce8a7cc776a39ad291d4648e3e39ae.hax
SEND FLAG AS TEXT
— END TRANSMISSION —

Si nos intentamos conectar por ssh trololo@54.171.128.20 -p 34342 vemos que el servidor responde, pidiendo clave pública. Descodificando el string de ACCESS de base64, vemos una referencia a una máquina remota (AWS) y esta keyword: openssh-key-v1nonenone3

Haciendo pruebas, detectamos que la clave sigue el algoritmo DSA (no RSA). La preparamos:

$ cat id_dsa
—–BEGIN OPENSSH PRIVATE KEY—–
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACD48tA2UHkNwf1gjzFoefbSiiA3s0+FIYWYOlxHuwDAVAAAALhSf19rUn9f
awAAAAtzc2gtZWQyNTUxOQAAACD48tA2UHkNwf1gjzFoefbSiiA3s0+FIYWYOlxHuwDAVA
AAAECeukBbUT2Vlozfd98BRRvKGCFRc0mdvRhAItlDfp1U7vjy0DZQeQ3B/WCPMWh59tKK
IDezT4UhhZg6XEe7AMBUAAAALnJvb3RAaXAtMTcyLTMxLTYtNjUuZXUtd2VzdC0xLmNvbX
B1dGUuaW50ZXJuYWwBAgMEBQYH
—–END OPENSSH PRIVATE KEY—–

Y conectamos:

$ ssh -i id_dsa -p 34342 trololo@54.171.128.20
Last login: Sat Mar 28 14:34:31 2020 from xxxxxxx
-sh-4.2$

Es una shell restringida (ni ls, ni gaitas). Además, si intentas borrar algo, te añade un espacio en blanco… En fin… El autocompletamiento con tabulador funciona. Vemos que existen algunos directorios, entre ellos /bin. Añadimos /bin al PATH y disponemos de cat, dig, ls y nsupdate.

El comando dig nos da una pista…. recordemos también que no hemos usado esta parte del enunciado:

IDENTIFY USING 20ce8a7cc776a39ad291d4648e3e39ae.hax

Investigando un poco vemos que existe un proyecto con TLD .hax para DNS dinámicos. La otra pista «SEND FLAG AS TEXT » nos parece indicar que necesitamos crear un registro TXT con la Flag. ¿Pero dónde está la flag? Bueno, no había muchos directorios en la máquina restringida donde nos encontramos, así que rastreando carpetas nos encontramos con /var/tmp/secret,

-sh-4.2$ cat /var/tmp/secret
Is0latI0nFl4w3dNetW0rkz!

Pedimos un dig del TLD hax:

dig -t txt 20ce8a7cc776a39ad291d4648e3e39ae.hax

y vemos que vamos bien:

-sh-4.2$ dig -t txt 20ce8a7cc776a39ad291d4648e3e39ae.hax

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-9.P2.amzn2.0.2 <<>> -t txt 20ce8a7cc776a39ad291d4648e3e39ae.hax
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12895
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;20ce8a7cc776a39ad291d4648e3e39ae.hax. IN TXT

;; AUTHORITY SECTION:
hax. 60 IN NS ns1.hax.

;; ADDITIONAL SECTION:
ns1.hax. 60 IN A 127.0.0.1

;; Query time: 0 msec
;; SERVER: 172.31.6.65#53(172.31.6.65)
;; WHEN: Sat Mar 28 16:41:53 UTC 2020
;; MSG SIZE rcvd: 136

Nos queda hacer el nsupdate … pero antes, veamos quién resuelve los DNS en esta máquina:

-sh-4.2$ cat /etc/resolv.conf
nameserver 172.31.6.65

$ nsupdate

server 172.31.6.65

update add 20ce8a7cc776a39ad291d4648e3e39ae.hax. 300 TXT «Is0latI0nFl4w3dNetW0rkz!»

send

Y listo! Pulsamos en el botón que nos ha preparado la ORG en ese level y pasamos de nivel