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 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.

HackIt! 2013. Level 5. Cifrado casero

crackle Nos pasan un archivo .rar con la contraseña del nivel 5 cifrada. Descomprimiendo el rar nos encontramos con una estructura de archivos como la siguiente:

entregable-dificil/
└── bin
    └── org
        └── euskal
            └── hackit
                ├── _2013
                │   ├── App.class
                │   ├── PasswordReveal.clazz
                │   ├── prepare
                │   │   └── PrepareHackit2013.class
                │   ├── TestClass1.class
                │   ├── TestClass1.clazz
                │   ├── TestClass2.class
                │   └── TestClass2.clazz
                └── crypt
                    ├── CryptUtil.class
                    └── FileClassLoader.class

Los .class los podemos intentar descompilar con jd-gui para ver qué hacen. Por ejemplo, App.class descompilado tiene esta pinta:

public class App
{
  public static void main(String[] args)
    throws Exception
  {
    ((Runnable)new FileClassLoader("bin", new CryptUtil(
      PasswordReveal.daKey), new String[] { "PasswordReveal", "TestClass1", 
      "TestClass2" }).loadClass(
      "org.euskal.hackit._2013.PasswordReveal").newInstance()).run();
  }
}

La clase FileClassLoader carga a partir del directorio pasado como primer parámetro las clases compiladas pasadas en el array de Strings (en el ejemplo PasswordReveal, TestClass1 y TestClass2) y las cifra con la clase CryptUtil (a la que pasa como parámetro una semilla que recoge del atributo estático PasswordReveal.daKey), dejando el resultado en un fichero con el mismo nombre y extensión .clazz.

Los TestClass1 y TestClass2 nos los dan como ejemplo, pero casualmente falta PasswordReveal.class 🙂 que parece ser que es la clase que contiene el password que nos interesa. Lo que sí tenemos es la clase cifrada PasswordReveal.clazz.

Si pudiéramos descifrarla, estaríamos muy cerca de pasarnos el nivel… De hecho, tenemos el .class de CryptUtil. Si lo descompilamos, encontraremos el método de cifrado (ojo, applyCript es el código descompilado y como tal tiene algunos errores fáciles de solucionar manualmente):

public class CryptUtil
 
 public static final long DA_2012_KEY = 4919L;
 static final long a = 1664525L;
 static final long c = 1013904223L;
 static final char m = ' ';
 long seed;
 long initial_seed;
 
 public CryptUtil(long seed)
 {
   this.seed = seed;
   this.initial_seed = seed;
 }
 
 public void applyCrypt(byte[] data)
 {
   this.seed = this.initial_seed;
   for (int i = 0; i < data.length; i++) {
     this.seed = ((1664525L * this.seed + 1013904223L) % 32L);
     byte mask = (byte)(int)(this.seed & 0xFF);
     int tmp46_45 = i;
     byte[] tmp46_44 = data; tmp46_44[tmp46_45] = ((byte)(tmp46_44[tmp46_45] ^ mask));
   }
 }

Parece que applyCrypt básicamente realiza un XOR entre el contenido del .class y una semilla pasada como parámetro. Supuestamente, si cogemos por ejemplo TestClass1.class y le aplicamos applyCrypt, deberíamos obtener TestClass1.clazz (y viceversa, al ser una simple función XOR!). Habrá que comprobarlo…

HackIt! 2013. Level 4. Trollencio

trollencio Este nivel nos trajo por la calle de la amargura. Tenemos como pista el cómic de la izquierda. Lo siguiente que nos dicen es que la solución está en este fichero. También tenemos esta otra imagen como pista…. ¿Qué es? ¿Un S.O.S.? No lo sabemos…

sosFinalmente, si sacamos los strings del fichero binario obtendremos algo de información. Se ve que hay una cadena que podría ser el password cifrado de alguna forma… también vemos un 2061 (¿tercera parte de la saga de una odisea del espacio?)  Pero no sabemos cuál es el método a usar para pasar esta fase. ¿Y cómo nos la pasamos nosotros? Jugando con lo que supuestamente debería ser el pass cifrado (M5O1V0R81)… pero no nos gustó el procedimiento, así que… ¿alguien (marcan, lothwen, abeaumont…) sabe cuál es la forma ortodoxa de superar este nivel? Agradecería comentarios al respecto, para que no se me quede esta espina clavada 🙂

HackIt! 2013. Level 3

2013-08-05 05_50_39-Hackit - Main - IBM NotesHack It 3: Post-It v.1989. Así se titula la prueba. El nivel tiene enlazado un fichero .nsf , es decir, una aplicación Lotus Notes (ahora se llama IBM Notes).  La extensión NSF es un acrónimo de Notes Storage Facility. En estos ficheros se guardan tanto el diseño de la aplicación como los datos asociados. Los datos son documentos  (llamados «notes») que se muestran al abrir el fichero .nsf. Normalmente las aplicaciones se acceden a través de un servidor (Lotus Domino) pero, tal y como se demuestra en este nivel, también pueden abrirse en modo local. No me extenderé en hablar sobre el uso de Lotus IBM Notes, ya tuve en otra vida una experiencia con esta bestia y es por ello que conocía el modo «Diseño» de Notes. En este modo se pueden editar las acciones (normalmente programadas en LotusScript) que se ejecutarán al abrir o cerrar la base de datos, al abrir un documento, periódicamente…

Lo más importante es saber cómo pasar este nivel. Lo primero (y creedme, lo más complicado) es conseguir instalar el cliente Notes. Hay una versión llamada Open Social (IBM Notes 9.0), descargable en modo trial para Linux, MacOSX y Windows. La versión para Linux es nativa… pero me costó horrores instalarla (problemas de dependencias con libgnomeprint, libbonobo, libgnome-desktop… En fin, una vez instalada, abrimos la aplicación password.nsf que nos enlazan en este nivel y vemo que muestra una aplicación con 2 columnas: fecha y datos. Además, salta un mensaje indicando que «Your word for today is:  una ristra de carácteres no imprimibles 🙂

Así que entramos en modo diseño desde Actions / View Options / Design… y aquí es donde nos damos cuenta de que la versión de Notes para Linux está sin terminar :-O No tiene modo «Design» (o yo no lo he encontrado). En fin… probemos con la versión Windows. Anda, en esta sí que está el modo diseño. Lo abrimos y vemos que el evento «postopen» desencadena la ejecución de un script que básicamente encadena los números de la segunda columna, tratándolos como carácteres ASCII y muestra el mensaje indicado. Ok, ¿pero de dónde salen estos números? Si examinamos el diseño de esa columna veremos un código como el siguiente:

@Modulo((Data*123);256);
REM{FIXME. 123 IS WRONG};

Es decir, cada número es igual a calcular el módulo 256 de la fecha de su izquierda (interpretada como número), multiplicada por 123. El problema es que 123 no es el número correcto… ¿qué podemos hacer?
1) Apuntar los números de las fechas en crudo (dejando por ejemplo «Data» como valor a imprimir, sin módulos ni gaitas)
2) Una vez que tengamos la ristra de números en crudo, crear un programa que pruebe todas las opciones posibles de X tales que @Modulo((Data*X);256); nos arroje un mensaje con sentido.

Piece of cake! Y así llegamos al temido «Trollencio», level 4, que nos tuvo a casi todos los grupos epatados (que no empatados 😉