¿Hará desaparecer al programador la IA generativa? (ii)

Ya escribí al respecto hace un tiempo: https://ikasten.io/2024/05/20/hara-desaperecer-al-programador-la-ia-generativa/

Pero hoy vengo con dos nuevos ejemplos.

Quería ejecutar la siguiente instrucción en SQLite

ALTER TABLE staff
ADD COLUMN IF NOT EXISTS username TEXT NOT NULL DEFAULT "";

pero SQLite no soporta la claúsula IF NOT EXISTS en ADD COLUMN.

Así que le pedí sugerencias a Claude 3.5 Sonnet y GPT-4o.

Ambos estaban de acuerdo en que esto funcionaría:

BEGIN TRANSACTION;

-- Check if the username column exists, if not, add it
SELECT CASE
    WHEN COUNT(*) = 0 THEN
        'ALTER TABLE staff ADD COLUMN username TEXT NOT NULL DEFAULT ""'
    ELSE
        'SELECT 1' -- Do nothing
END
FROM pragma_table_info('staff')
WHERE name = 'username';

Parece un buen truco: se comprueba en los metadatos de SQLite a ver si existe una columna username (se cuenta cuántas veces existe) y si el conteo es 0, entonces hay que ejecutar el ADD COLUMN.

Pero, hemos sido engañados.

El SQL es sintácticamente correcto pero la orden dentro del THEN es simplemente para mostrar un string, no para ejecutar el ALTER TABLE.

Cuando te digan que los ingenieros software no existirán en 5 años, acuérdate de esto. O de esto otro:

HackIt / Level 4 / EE32 / WriteUp

Veamos de qué tipo de archivo se trata:

$ file mystery_file
mystery_file: SQLite 3.x database, last written using SQLite version 0, page size 1024, file counter 4, database pages 0, cookie 0x2, schema 1, UTF-8, version-valid-for 0

Es un archivo que contiene una base de datos SQLite.

$ sqlite3 mystery_file
SQLite version 3.43.2 2023-10-10 13:08:14
Enter ".help" for usage hints.
sqlite> .schema
CREATE TABLE idx (val text);
CREATE TABLE data (id integer primary key asc, val blob);

Analicemos el contenido:

sqlite> select * from idx;
1c9a44eb2e8eaf3da1eb551da310cce7
sqlite> select * from data LIMIT 3;
1|�
2|�
3|�

La tabla idx solo tien un columna val con una única fila: 1c9a44eb2e8eaf3da1eb551da310cce7

Buscando en Google, vemos que ese hash corresponde al string ‘lost’

Respecto a los binarios guardados en la tabla data:

Podemos copiar esos valores hexa, convertirlos a binario y generar un fichero, que podremos analizar con el comando file:

$ echo "1f 8b 08 00 7f f9 96 4a 02 ff 53 50 20 04 b8 00 7d 5c bc 09 23 00 00 00" | xxd -r -p > output.bin
$ file output.bin
output.bin: gzip compressed data, last modified: Thu Aug 27 21:24:15 2009, max compression, truncated

Pare que son 100 blobs gzip. Saquemos todos ellos:

import sqlite3

# Connect to the SQLite database
conn = sqlite3.connect('mystery_file')
cursor = conn.cursor()

# Query to fetch all blobs from the data table
cursor.execute("SELECT val FROM data")
rows = cursor.fetchall()

# Concatenate all blobs into a single file
with open("combined_blobs.gz", "wb") as file:
    for row in rows:
        file.write(row[0])

# Close the connection
conn.close()

print("All blobs have been concatenated into 'combined_blobs.gz'.")

Tras descomprimir con gunzip el fichero combined_blobs.gz:

Y tratando de descifrar ese bonito ASCII art estuvimos más de 24 horas. Sacamos todas las posibles combinaciones (ordenando por longitud de cada línea y agrupando las de la misma longitud). Pero… había una forma más fácil o de idea feliz.

Resulta que si buscamos el ‘lost’ de la tabla idx con la palabra números:

Resulta que en la serie Lost esos números son recurrentes… ¿Y si sacamos los blobs en ese orden?

import sqlite3

# Connect to the SQLite database
conn = sqlite3.connect('mystery_file')
cursor = conn.cursor()

# Select the specific rows with the provided indices
indices = [4, 8, 15, 16, 23, 42]
blobs = []

for index in indices:
    cursor.execute("SELECT val FROM data WHERE rowid=?", (index,))
    row = cursor.fetchone()
    if row is not None:
        blobs.append(row[0])

# Concatenate the selected blobs into a single file
with open("selected_combined_blobs.bin", "wb") as file:
    for blob in blobs:
        file.write(blob)

# Close the connection
conn.close()

print("Selected blobs have been concatenated into 'selected_combined_blobs.bin'.")

Vaya… estuvimos 24 horas deambulando cuando la solución estaba a nuestro alcance con una ‘simple’ búsqueda:

SolveIt / Level 3 / 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 🙂

Este tercer nivel, aunque era relativamente sencillo, quizá más que el anterior, no lo han resuelto tantos equipos.

El enunciado parece ser un acertijo

“1111111111111111111 tres primos son y juntos les gusta ir”.

La primera pregunta que nos hicimos: ¿este número, interpretado como decimal, es primo? Sí, lo es.

La segunda: ¿este número, interpretado como binario, es también primo? Si lo pasamos de binario a decimal, obtenemos 524287, que también es primo.

Tenemos dos primos, nos falta el tercero. Si contamos el número de unos, observamos que son 19, así que parece que lo tenemos.

Recapitulando, tenemos 3 primos:

  • 1111111111111111111
  • 524287
  • 19

También sabemos que les gusta ir juntos, pero no sabemos en qué orden. Podemos ir problando, aunque, por ser ordenados, podría tener sentido que fueran de menor a mayor, tal que así:

195242871111111111111111111

Ahí está la respuesta que buscábamos; los tres primos, juntos, en familia.

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

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