HackIt!2013. Level 7. Old School Spectrum (y II)

Fuse_pokedAnalicemos el código. La línea 15 define la función módulo (no existente en el Basic del Spectrum). La línea 20 declara e inicializa ciertas variables que luego veremos. La línea 30 pide al usuario un password (Code). La línea 40 comprueba que la longitud del pass introducido sea al menos igual a 6 (las letras de «HackIt»). Si fuera menor, concatena el pass hasta que se cumpla la condición. En la línea 50 comienza la chicha. Iteraremos 6 veces. En cada iteración, la línea 55 del programa cambiará el contenido de la línea 60, sobre-escribiendo en memoria 3 posiciones (como si fuera el código de un virus polimórfico). ¿Cómo sabemos qué es lo que escriben esos POKE y dónde? Poniendo un STOP justo al comienzo de la línea 70 (ver figura de la izquierda).

10 REM PoliMorph
15 DEF FN m(a,b) = a- (INT (a/b)*b) : REM a Mod b
20 LET pos1=00053: LET pos2=00069: LET pos3=24000: LET x1=181: LET x2=42: LET x3=45
30 LET n$="HackIt": INPUT "Code:";c$: LET x$=""
40 IF LEN c$< LEN n$ THEN LET c$=c$+c$: GOTO 40 
50 FOR g=1 TO LEN n$ 
55 POKE pos1,x1: POKE pos2,x2: POKE pos3,x3 
60 LET cod = INT (98*ATN (g/ LEN n$)) + CODE c$(g)/0 + g 
70 LET x1=x1+1: IF x1>183 THEN LET x1=181
80 LET x2=x2+1: IF x2>47 THEN LET x2=42
90 LET x3=x3+2: IF x3>45 THEN LET x3=43
110 LET x$=x$ + CHR$ (32 + FN m(cod, 95))
120 NEXT g: PRINT "Decoded:";x$
130 IF n$ <> x$ THEN PRINT "Wrooonnggg!": FOR g=pos1-11 TO pos3+2: POKE g,48+ INT (RND*10): NEXT g: STOP
140 PRINT "Right!, here goes the rest..."

Vemos que tras la ejecución, la línea 60 ha cambiado:

Antes:
60 LET cod = INT (98*ATN (g/ LEN n$)) + CODE c$(g)/0 + g
Ahora, tras los POKE(pos_memoria, valor):
60 LET cod = INT (98*ASN (g/ LEN n$)) + CODE c$(g)*0 - g

¿Por qué? Las posiciones de memoria las calculó pacientemente el autor de la prueba (thEpOpE). ¿Y el valor? Podemos ayudarnos de esta tabla de mnemónicosPor ejemplo, el primer POKE(00053, 181) cambia la posición 00053 (donde teníamos un ATN -arcotangente-) por el operador ASN -arcoseno-, dado que al código 181 le corresponde el operador ASN según la tabla indicada. La división por 0 (ilegal), ha cambiado a multiplicación por 0 (legal) con POKE(00069, 42). El «+ g» ha cambiado a «- g» con el POKE(24000, 45). El 45 corresponde al operador «-«. Lo que no tengo claro es cómo demonios la dirección 24000 corresponde a esa sección del código (seguro que thEpOpE nos ilumina en los comentarios 🙂

Los valores 181, 42 y 45 corresponden a las variables x1, x2 y x3 definidas en la línea 20. En cada vuelta del bucle, x1, x2 y x3 cambiarán, por lo que los POKE modificarán la línea 60 en cada vuelta. La tabla enlazada nos ayudará a conocer qué hace en cada vuelta. Esa línea 60 va modificando cada letra de nuestro password (dejándola en la variable cod). En la línea 110 se calcula el carácter asociado al código ASCII de 32 + cod módulo 95. Esto nos dará un carácter imprimible entre ASCII(32) y ASCII (32+94). Ese carácter, en cada vuelta, debe coincidir con las letras del string «HackIt». ¡Un level muy trabajado!.

Algunos comentarios finales. Me ha gustado el efecto de bombardear la memoria y cambiar el código en caso de introducir un pass incorrecto (línea 130). También nos dejó ojipláticos al comienzo la división por cero de la línea 60. Cuando vimos los POKE de la 55 supusimos que corregiría ese bug intencionado. También nos costó encontrar un emulador que cargara el código en BASIC. No sé si fue un efecto buscado o no…

Finalmente, el level tenía un bug no intencionado. La clave final aparece en los strings del binario… nos dimos cuenta tras pasar horas y horas con la prueba, encontrar la solución de forma ortodoxa y recordar que ese string lo habíamos visto antes en algún sitio O:-)

Y con esto, llegamos al level 8, donde nos espera un interesante intérprete RPN del mismo autor…

HackIt!2013. Level 7. Old School Spectrum

Fuse - SDL Spectrum emulatorLOAD «» . Ése es el mantra que recuerdo haber repetido cientos de veces en 1986. El nivel 7 del HackIt! de este año me hizo recordar algunos comandos más… pero para comenzar a recordar, lo primero que hay que hacer es instalar un emulador del viejo Spectrum en nuestros flamantes portátiles. Fuse-SDL es uno de ellos, disponible desde apt-get en Ubuntu. Desde aquí, pulsando F1 accederemos a un menú desde el que poder cargar el fichero que nos pasan: image.dsk. Elegimos a continuación la opción +3 BASIC.

Para ver el contenido del disco, tecleamos:

cat

En este caso, únicamente veremos un fichero de nombre HACKME.BAS. Por cierto, lo del disco fue una innovación que trajo tío Sinclair con la versión +3. Yo no llegué a disfrutarla, pasé directamente del casete a discos de 5-1/4. ¡Menudo adelanto! 🙂  En fin… batallitas.

Ahora hay varias vías. La primera que probamos es a cargar el código con:

LOAD "HACKME.BAS"

Esto cargará en memoria y ejecutará el programa en Basic HACKME.BAS. Es importante el último punto: ejecutará directamente, antes de mostrarnos el código fuente. La ejecución nos pide que introduzcamos un código (vamos a llamarle «password»). Internamente el programa hace algunas operaciones y nos devuelve otro string calculado a partir de nuestro password (¿cómo lo ha calculado? ése es el quid de la cuestión), junto a un mensaje del estilo «Wrooong!».

Pulsando ENTER tras el mensaje «Wrooong!» pasamos a ver el código fuente… y aquí empieza la diversión. El programa lleva por título «PoliMorph» y vemos, entre otras cosas, que en caso de introducir un password incorrecto, ejecuta la instrucción POKE (escritura en memoria) justo apuntando al propio código en BASIC, sobreescribiendo con números al azar multitud de posiciones (lo que provoca que perdamos el código original). Lo de Polimorph no era broma…

Así que un primer paso es obtener el código de HACKME.BAS _antes_de su ejecución. Bien, esto fue fácil, basta con usar el comando MERGE «HACKME.BAS» (en lugar de LOAD). El comando MERGE carga el código en memoria pero no lo autoejecuta. Y aquí aparece esta pequeña obra de arte, con más comandos POKE haciendo de las suyas.  El lector más despierto seguro que verá algo muy extraño en la línea 60 (¿una división por 0?):

10 REM PoliMorph
15 DEF FN m(a,b) = a- (INT (a/b)*b) : REM a Mod b
20 LET pos1=00053: LET pos2=00069: LET pos3=24000: LET x1=181: LET x2=42: LET x3=45
30 LET n$="HackIt": INPUT "Code:";c$: LET x$=""
40 IF LEN c$ < LEN n$ THEN LET c$=c$+c$: GOTO 40  
50 FOR g=1 TO LEN n$ 55 POKE pos1,x1: POKE pos2,x2: POKE pos3,x3  
60 LET cod = INT (98*ATN (g/ LEN n$) + CODE c$(g)/0 + g  
70 LET x1=x1+1: IF x1>183 THEN LET x1=181
80 LET x2=x2+1: IF x2>47 THEN LET x2=42
90 LET x3=x3+2: IF x3>45 THEN LET x3=43
110 LET x$=x$ + CHR$ (32 + FN m(cod, 95))
120 NEXT g: PRINT "Decoded:";x$
130 IF n$ <> x$ THEN PRINT "Wrooonnggg!": FOR g=pos1-11 TO pos3+2: POKE g,48+ INT (RND*10): NEXT g: STOP
140 PRINT "Right!, here goes the rest..."

El código es bastante legible… salvo la línea 55 y esa división por 0 en la 60… ¿Te animas a descifrar cuál es el funcionamiento exacto?

Otro detalle más: probamos con otros emuladores de Spectrum con soporte para archivos .DSK sin éxito. Por alguna razón sólo fuse-sdl fue capaz de leer el código de HACKME.BAS.

HackIt! 2013 . Level 6. Expresiones regulares y sed (y II)

Éste es el script comentado del nivel, línea a línea:

#!/bin/sed -rnuf
s/.*/Password:/; # sustituir la primera línea por Password:
p; #imprimir 
n; # leer siguiente línea
s/[^a-zA-Z0-9]/_/g; # sustituir todo carácter no alfanumérico por _
s/./ /g; # añadir un espacio en blanco tras cada carácter
s/[0-9] /0/g; # añadir un prefijo de 0 a todos los dígitos
s/[a-p] /1/g; # añadir un prefijo de 1 a todas las letras entre a y p
s/[q-z] /2/g; # añadir un prefijo de 2 a todas las letras entre q y z
s/[A-P] /3/g; # añadir un prefijo de 3 a todas las letras entre A y P
s/[Q-Z] /4/g; # añadir un prefijo de 4 a todas las letras entre Q y Z
y/abcdefghijklmnopqrstuvwxyz/0123456789abcdef0123456789/; # replace cada carácter de la primera ristra por su equivalente de la segunda ristra
y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789abcdef0123456789/; # sustituir a por b
s/_/50/g; # sustituir _ por 50
s/(.)(.) /21/g; # invertir: "XY " pasa a ser "YX "
x; # intercambiar buffer y pattern (buffer sólo contiene un salto de línea) 
s/$/-----/g; # el salto de línea se cambia por 5 guiones
s/-/---/g; # ahora tenemos 3*5 = 15 guiones
s/-/----/g; # ahora tenemos 15*4 guiones
s/-/---/g; # ahora tenemos 15*4*3 guiones = 180 en total
x; # intercambiar buffer y pattern (guardamos los 180 guiones y recuperamos pattern)
:x; # label :x  (es un destino de salto)
y/072b346d18a9f5ce/143c527e9ab0d6f8/; # replace cada carácter de la primera ristra por su equivalente de la segunda ristra
ta; # salta a a: si el último s/// tuvo éxito
:a; # label :a (es un destino de salto)
s/[^0x]/x/g; # si el carácter actual no es un 0 ni una x, sustituirlo por x0.
                                         # P. ej. 051 quedaría como 0x5x1
y/fedcba987654321/edcba9876543210/; # find&replace
ta; # va a saltar a la etiqueta "a" si el anterior s/// tuvo éxito... 
s/(x*)0(x*)0/12020/g; # sustituir xxxxx0xxxxxx0 por xxxxxxxxxxx0xxxxxxx0
s/x{16}//g; # eliminar apariciones de 16x  seguidas
s/x{15}0/f/g; # sustituir 15x0 (xxxxxxxxxxxxxxx0) por f
s/x{14}0/e/g; # sustituir 14x0 por e
s/x{13}0/d/g; # sustituir 13x0 por d
s/x{12}0/c/g; # sustituir 12x0 por c
s/x{11}0/b/g; # sustituir 11x0 por b
s/x{10}0/a/g; # sustituir 10x0 por a
s/x{9}0/9/g;  # sustituir 9x0 por 9
s/x{8}0/8/g; # sustituir 8x0 por 8
s/x{7}0/7/g; # sustituir 7x0 por 7
s/x{6}0/6/g; # sustituir 6x0 por 6
s/x{5}0/5/g; # sustituir 5x0 por 5
s/x{4}0/4/g; # sustituir 4x0 por 4
s/x{3}0/3/g; # sustituir 3x0 por 3
s/x{2}0/2/g; # sustituir 2x0 por 2
s/x{1}0/1/g; # sustituir 1x0 por 1
s/^(0)(.{3})(.*)/132/;
s/^(1)(.{8})(.*)/132/;
s/^(2)(.{7})(.*)/132/;
s/^(4)(.{2})(.*)/132/;
s/^(6)(.{10})(.*)/132/;
s/^(8)(.{9})(.*)/132/;
s/^(a)(.{11})(.*)/132/;
s/^(c)(.{5})(.*)/132/;
s/^(e)(.{16})(.*)/132/;
s/(...)(.*)/21/;
tz;
:z;
x;
s/-//;
x;
tx;
s/^017c43a81ddb8b638fb3a32c51f4$/Win!/;
Tf;
p;
q;
:f;
s/.*/You Fail It!/;
p;
q

En resumidas cuentas, le pasamos una entrada al script sed (de 2 líneas, la primera puede ser cualquier cosa, porque la va a sustituir por «Password:»), ciclamos 181 veces (una por cada guión y una inicial antes de llegar a la condición de salto) por una serie concreta de sustituciones y al finalizar, comprobamos que la cadena que nos queda después de tanta sustitución es, exactamente ésta: «017c43a81ddb8b638fb3a32c51f4». Si así fuera, escribimos (comando ‘p’ de sed) «Win» y salimos (comando ‘q’). Si no fuera así, saltamos a la etiqueta «f» (comando Tf, es decir, un salto condicionado a que la última sustitución no tuviera éxito). En la etiqueta f sustituimos todo por «You Fail It!», lo escribimos y terminamos.

Me han gustado dos cosas: el uso de etiquetas y saltos condicionales por un lado (desconocía este aspecto), y la forma de controlar la condición del bucle. En concreto, desconocía el uso del comando «x», que viene a decir: coge lo que tenemos en el buffer y pónlo en el cursor del patrón que estamos analizando (lo sustituye, es decir, la línea que estábamos analizando pasa al buffer):

x; # intercambiar buffer y pattern (buffer sólo contiene un salto de línea) 
s/$/-----/g; # el salto de línea se cambia por 5 guiones

Inicialmente en el buffer sólo hay un fin de línea, así que tras hacer x y «s/$/—-/g», nos quedamos con una línea de 5 guiones. Como necesitamos 180, usamos este truco:

s/-/---/g; # ahora tenemos 3*5 = 15 guiones
s/-/----/g; # ahora tenemos 15*4 guiones
s/-/---/g; # ahora tenemos 15*4*3 guiones = 180 en total

Lo dicho, me gustó el planteamiento del problema. Pero claro, ¿cómo encontrar la cadena inicial que se usó para, tras todas las sustituciones indicadas, obtener «017c43a81ddb8b638fb3a32c51f4»? Bueno, invirtiendo el proceso de sustituciones. Se puede hacer con el propio sed, pero mis compañeros se curraron un script en VBA que resolvía al 95% (son magos del Excel…).

Con ese script conseguimos saber cómo debía estar formado el string de entrada correcto justo después de la instrucción «s/_/50/g;» . Si justo después de esa instrucción introducimos ésta otra:

s/(.*)/22 43 41 03 10 1c 23 04 12 24 1b 00 24 42 /;

y ejecutamos el script (recordad dadle permisos de ejecución al crackme):

$ echo -e "cualquier cosancualquier cosa" | ./crackme
Password:
Win!

Ya sólo queda darle un poco a la pelota para saber cómo interpretar las primeras líneas del script sed para que nos salga ese string mágico. Es cuestión de pensar unos minutos y hacer un script rápido para probar algunas combinaciones (un 2 en el string «mágico» puede ser porque inicialmente tenías una ‘c’ o una ‘s’ en la entrada:
y/abcdefghijklmnopqrstuvwxyz/0123456789abcdef0123456789/; Cuando tengas más de una opción, prueba a escribirlas todas – con un script – y verás que una de ellas se lee fácil 😉

Al pasar esta prueba llegarás al level 7, donde tendrás que desempolvar tus conocimientos de la era Spectrum!

HackIt! 2013 . Level 6. Expresiones regulares y sed

«Se te dan bien las expresiones regulares, ¿no? ;)» Así empieza el enunciado del level 6. Un level (IMHO) jodido al que mis compañeros Joserra y Beñat estuvieron, durante unas cuantas horas, dándole vueltas, mientras el resto nos devanábamos los sesos con el level de la calculadora polaca inversa (RPN)…

Nos pasan el siguiente churro:

#!/bin/sed -rnuf
s/.*/Password:/;p;n;s/[^a-zA-Z0-9]/_/g;s/./ /g;s/[0-9] /0/g;s/[a-p] /1/g;s/[q-z] /2/g;s/[A-P] /3/g;s/[Q-Z] /4/g;y/abcdefghijklmnopqrstuvwxyz/0123456789abcdef0123456789/;y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789abcdef0123456789/;s/_/50/g;s/(.)(.) /21/g;x;s/$/-----/g;s/-/---/g;s/-/----/g;s/-/---/g;x;:x;y/072b346d18a9f5ce/143c527e9ab0d6f8/;ta;:a;s/[^0x]/x/g;y/fedcba987654321/edcba9876543210/;ta;s/(x*)0(x*)0/12020/g;s/x{16}//g;s/x{15}0/f/g;s/x{14}0/e/g;s/x{13}0/d/g;s/x{12}0/c/g;s/x{11}0/b/g;s/x{10}0/a/g;s/x{9}0/9/g;s/x{8}0/8/g;s/x{7}0/7/g;s/x{6}0/6/g;s/x{5}0/5/g;s/x{4}0/4/g;s/x{3}0/3/g;s/x{2}0/2/g;s/x{1}0/1/g;s/^(0)(.{3})(.*)/132/;s/^(1)(.{8})(.*)/132/;s/^(2)(.{7})(.*)/132/;s/^(4)(.{2})(.*)/132/;s/^(6)(.{10})(.*)/132/;s/^(8)(.{9})(.*)/132/;s/^(a)(.{11})(.*)/132/;s/^(c)(.{5})(.*)/132/;s/^(e)(.{16})(.*)/132/;s/(...)(.*)/21/;tz;:z;x;s/-//;x;tx;s/^017c43a81ddb8b638fb3a32c51f4$/Win!/;Tf;p;q;:f;s/.*/You Fail It!/;p;q

Precioso, ¿eh?. Bien, antes de llegar a este nivel creía que controlaba algo del editor de flujos de texto sed («sed – stream editor for filtering and transforming text»), pero cuando ví este script me dí cuenta que aún me faltaban «algunos» conocimientos al respecto. En fin, vayamos por partes.

Sed nos permite manipular texto desde la línea de comandos con scripts concisos y la ayuda de expresiones regulares (mmmh… no me meteré a explicar qué son las expresiones regulares, hay libros completos para ello, bastante tengo con sed 🙂

Por ejemplo, para pasar de «esto es una prueba» a «esto una prueba es», podríamos ejecutar lo siguiente:

echo "esto es una prueba" | sed -re 's/(.*) es(.*)/12 es/'

-r = expresiones regulares extendidas
-e = editar en línea (en lugar de meter el script sed en un fichero)

La primera línea del script que nos pasan es:

#!/bin/sed -rnuf

El shebang y la ruta al ejecutable de sed, con los parámetros -rnuf.

-n = quiet (que no escriba automáticamente el resultado de ejecutar cada línea del script sed, como hace por defecto. Si dentro del script se quisiera imprimir, habría que ejecutar el comando ‘p’ de sed)
-u = unbuffered (no tengo clara esta opción… se supone que ésto hace que sed lea cantidades mínimas de la entrada estándar y escriba por salida estándar los resultados ‘más frecuentemente’, sin almacenar resultados en buffers, pero no sé exactamente cómo afecta al script)
-f = el script viene a continuación, en este mismo fichero

Lo primero que hay que hacer es formatear y comentar el script, para ver qué demonios hace. Por ejemplo, las primeras líneas:

s/.*/Password:/; # lee la primera línea que le pasemos y la sustituye por 'Password:'
p; # escribe el resultado de la sustitución (es decir, escribe 'Password:')
n; # salta a la siguiente línea de entrada estándar

¿Te animas a seguir descifrando el script? Una vez que lo hagas, comenzarás a entender qué es lo que se supone que hay que hacer…

HackIt! 2013. Level 5 (y II)

Sólo nos queda arreglar un poco el método de cifrado:

  public byte[] applyCrypt(byte[] data)
  {
    this.seed = this.initial_seed;
    byte[] tmp46_44 = data;
 
    for (int i = 0; i < data.length; i++) {
      this.seed = ((1664525L * this.seed + 1013904223L) % 32L);
      byte mask = (byte)(int)(this.seed & 0xFF);
        tmp46_44[i] = ((byte)(tmp46_44[i] ^ mask));
    }
    return tmp46_44;
  }

Cifrando TestClass1.class con ese método debería darnos exactamente TestClass1.clazz… pero no lo hace :-O Pasando un md5 sobre nuestro .clazz y el que nos pasan en el level, vemos que son distintos. Aquí nos quedamos un tanto desconcertados, pero @ochoto resolvió: el problema es la semilla… no es la que nos dan (4919) sino otro número primo. ¿Cuál? Aquél que nos permita ejecutar applyCrypt sobre TestClass1.class y nos devuelva exactamente el mismo TestClass1.clazz que el fichero de ejemplo (o viceversa, recordemos…).

Una vez conseguido, basta con aplicar applyCrypt() a PasswordReveal.clazz, con lo que conseguiremos PasswordReveal.class. Descompilando con jd-gui obtendremos el ansiado password al siguiente nivel.

UPDATE 09/08/2013: no dejéis de visitar el blog de cymo, el autor de la prueba, para ver en detalle la solución «ortodoxa» de este nivel.