Latest Tweets

UAM’s MARVEL CTF Episodio 2: WriteUp

Obtención del binario y del servidor

Descargamos y descomprimimos el archivo ZIP. Nos encontramos con un volcado de memoria que podemos analizar con Volatility. Listamos los procesos en ejecución y nos encontramos con un Netcat:

0xfffffa800685b860 nc64.exe 1940 2304 2 72 1 0 2018-12-20 15:47:56 UTC+0000

Para obtener el servidor al que se conecta usamos netscan:

volatility -f image.raw –profile=Win7SP1x64 netscan|egrep –color=yes 1940

Volatility Foundation Volatility Framework 2.6
0x13d880880 TCPv4 172.16.233.139:49166 34.247.69.86:9009 ESTABLISHED 1940 nc64.exe

Ya tenemos el servidor y el puerto al que se conecta: 34.247.69.86:9009. Para encontrar el programa, nos dedicamos a listar archivos de la MFT que estén en el directorio del usuario, probamos suerte con Desktop y nos encontramos con el archivo HydralarioHydra y un archivo flag.txt:

volatility -f image.raw –profile=Win7SP1x64 filescan |egrep –color=yes Desktop

0x000000013d563f20 16 0 R–r– \Device\HarddiskVolume1\Users\admin\Desktop\HydralarioHydra
0x000000013dfcb730 16 0 RW—- \Device\HarddiskVolume1\Users\admin\Desktop\flag.txt

Reversing Hydra

Obtención de información sobre el binario

En el mundo del reversing y del exploiting, el primer paso suele ser obtener la máxima información posible sobre el binario a analizar. Podemos tirar de las binutils (readelf, objdump, etc), o apoyarnos en herramientas y scripts más automatizados. Para empezar, obtenemos el tipo de arquitectura de este binario con el comando file:

file hydra.o
hydra.o: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=c03cee4c7f44b1055031fd53980bd22e47873ab1, not stripped

Disponemos de los símbolos (not stripped), lo que nos facilitará muchísimo el reversing. Por otro lado, es un binario dinámico ELF (Linux) con arquitectura Intel X86. Si el binario fuese estático, tendríamos toda la GNU Libc embedida dentro del ejecutable, con lo que el tamaño del mismo sería mayor. Además, durante el reversing, veríamos un montón de símbolos adicionales que podrían despistarnos (si no estamos habituados al reversing).

Teniendo en cuenta que debemos realizar exploiting sobre este binario, vamos a comprobar el tipo de protecciones que tiene activadas. Para esto se pueden usar infinidad de herramientas (readelf, rabin2, etc) pero yo suelo utilizar checksec.sh (https://www.trapkit.de/tools/checksec.html). Lanzamos el script contra el binario y observamos que tiene activadas 2 protecciones (Partial RELRO y NX):

Comprobando las protecciones del binario hydra.o

Vamos a determinar sobre que versión de Sistema Operativo se compiló el binario. Los compiladores suelen añadir un comentario a la sección .comment del binario ELF con un string identificativo. GCC, por ejemplo, usa esa convención. Utilizamos el comando readelf para leer la sección:

objdump -s –section .comment hydra.o

hydra.o: file format elf32-i386

Contents of section .comment:
0000 4743433a 20285562 756e7475 20372e33 GCC: (Ubuntu 7.3
0010 2e302d32 37756275 6e747531 7e31382e .0-27ubuntu1~18.
0020 30342920 372e332e 3000 04) 7.3.0.

Tal y como podemos observar, el binario se ha compilado usando GCC 7.3 sobre un Ubuntu 18.04 (Bionic Beaver). Tenemos incluso el nombre del paquete del compilador exacto: gcc-7.3.0-27ubuntu1~18.04.debEsto nos puede ser útil para determinar con mayor exactitud el tipo de sistema que está ejecutando el servidor de la UAM (versión de Sistema Operativo, compilador, versión de la GNU Libc6, … )

Estrategia de exploiting

A partir de las protecciones del binario, podemos ir haciéndonos una idea de las diferentes posibilidades de explotación disponibles a grosso modo, sin entrar todavía en detalles.

Con Partial RELRO podríamos llegar a sobrescribir la tabla .got.plt para, por ejemplo, substituir cualquier llamada a una función de la GNU Libc dentro del binario por otra. Este es el clásico ejercicio de exploiting para lograr ejecutar una shell haciendo un bypass de la protección NX (que está activada). Por ejemplo, si este binario llama a printf(), bastaría con substituir la llamada a printf() por system(), por ejemplo, y asegurarse de manipular correctamente el stack para que system() recibiera como parámetro un puntero a la cadena “/bin/sh”. Esto se conoce como ret2libc, un caso particular de ROP (ver siguiente párrafo).

Como tenemos protección NX, no podemos dejar un shell-code en la pila y ejecutarlo (obtendríamos un segmentation fault). Pero sí podríamos utilizar la técnica ROP para, a partir de byte-codes existentes en el binario, montar nuestra propia pila de ejecución de tal modo que logremos ejecutar código bajo nuestro control a pesar de la protección NX. ROP es bastante complejo, por eso podemos apoyarnos de herramientas que nos ayuden en la búsqueda de los ROP gadgets.

Ahora toca analizar el binario con Radare2, para ver qué vulnerabilidades tiene.

Reversing con Radare2

Abrimos el binario dentro de radare2 y lanzamos el comando aa para que ejecute su analizador más básico a la búsqueda de las funciones disponibles en el binario. Una vez tenemos las funciones identificadas por r2 podemos ver una lista de las mismas con el comando afl:

Listado de funciones en hydra.o

En reversing, algunas veces se encuentran funciones que no son llamadas por el flujo principal del programa. La función main() es la que siempre se ejecuta primero, así que podemos generar el Function Call Graph de la función main() para determinar el flujo del programa y ver si alguna función de las identificadas por r2 no es llamada nunca. Para ello usaremos el comando agc con el OFFSET de la función main:

Function Call Graph de la función main() de hydra.o

Si nos fijamos en el listado obtenido de funciones con el comando afl, nos damos cuenta de que la función sym.a() no es llamada nunca por el programa. Observando el Function Call Graph anterior, vemos que el programa llama a read_flag(), luego imprime algo por pantalla con una llamada a la función importada de la libc puts(), sigue con una llamada a la función check_age(), imprime algo por pantalla con la importación de la función printf() de la libc, y finalmente llama a tell_me_a_secret(). Nos aseguramos de que realmente no hay ninguna referencia a sym.a() con el comando axt @ sym.a:

Las funciones indicadas como sym.imp en r2 son funciones importadas de librerías externas al código del binario analizado.

¿Qué tiene la función sym.a()? Podemos ver su código desensamblado mediante el comando pdf y el correspondiente OFFSET en r2:

La función a() que nunca es llamada en hydra.o

Esta función, a priori, nos dice poco. Pero analizarla nos puede ir muy bien para más tarde. Tal y como se ve en el código de arriba, esta función recibe un parámetro (arg_8h). r2 lo identifica como integer porque será un puntero de 32 bits (4 bytes) a una dirección de memoria. Este valor se apilará en la pila justo antes de hacer una llamada a printf (fijaos en los OFFSETS 0x084142f4 y 0x084142f7). Por supuesto, r2 identifica este parámetro pasado a a() como el primer parámetro de la función printf(). La convención de llamadas en X86 es de tipo cdecl, por lo que los argumentos a las funciones se pasan a través de la pila de derecha a izquierda (como en el código de printf(), aunque sólo tiene un parámetro).

La siguiente función que nos miramos es read_flag(). Es código desensamblado habitual para abrir un archivo, gestionar el error si no existe o tiene 0 bytes, y leer el contenido del archivo a una posición de memoria. Podemos ver el código de la función read_flag con el comando pdf y su OFFSET. Mostramos la parte dónde, si se ha podido leer del archivo, se hace una llamada a strcpy() para copiar sobre obj.flag el contenido de local_14h, que contiene los bytes leídos del archivo “flag.txt”:

En las siguientes instrucciones se copia el valor leído del flag en la posición de memoria obj.flag.

Ya hemos comentado que la convención de llamadas es cdecl. En este caso, para llamar a strcpy() que se define como strcpy(char *dst, const char *src) , el código nos muestra como se apila el segundo parámetro primero (const char *src, esto es local_14h) en la pila y después se hace lo propio con el primer parámetro (char *dst, o sea obj.flag). En resumen, la llamada a read_flag() nos dejará el valor del flag en la posición de memoria apuntada por obj.flag.

Program Independent Code (-fPIC)

Si nos fijamos en el código desensamblado de las funciones main, read_flag, a, etc, veremos que siempre hay una llamada que se nos antoja, a priori, algo esotérica. Estamos hablando del call __x86.get_pc_thunk.bx. Por ejemplo, si miramos el código de la función main con: pdf @ main, veremos lo siguiente:

Llamada a sym.__x86.get_pc_thunk.bx para direccionar memoria a partir del PC.

Esto simplemente indica que este binario ha sido compilado con el flag -fPIC (Program Independent Code). Un binario PIC puede ser cargado en cualquier posición de memoria, y cualquier direccionamiento a posiciones de memoria se harán relativas al Program Counter o dirección de instrucción a ejecutar. Esto es así porque el código no puede saber, a priori, en que posición de memoria se estará ejecutando. Por ejemplo, el código de la función main de arriba. Tras la ejecución de read_flag(), el argumento a la función puts() que es apilado en el stack se obtiene a partir del direccionamiento [ebx-0x1aac], y el registro ebx en este momento, tras las dos primeras instrucciones del snippet anterior, contiene la dirección del Program Counter. Por regla general, las 2 primeras instrucciones se pueden leer en código de alto nivel como get_pc(program counter). 

Primer escollo: check_age()

Seguimos con el análisis de la siguiente función que es llamada por main(), check_age(). Antes de mirarnos esta función con detalle, observamos que es llamada desde main() y, después, su valor de retorno es guardado en la posición de memoria local_9h y comparada con 0. Si el valor es 0, entonces salta al OFFSET 0x08414428 dónde imprime algo por pantalla y termina la ejecución del programa (en el OFFSET 0x0841443a se puede leer lo que equivaldría a un return 0; desde main en C). Por lo tanto, si el valor devuelto por check_age() es 0, se salta la llamada a la función “tell_me_a_secret()”, lo que a priori no parece una buena idea:

Si check_age() == 0, entonces nos saltamos la llamada a tell_me_a_secret().

Si obtenemos el código desensamblado de check_age() con pdf @ check_age, veremos que en el OFFSET 0x0841427c se asigna 1 a EAX (valor de retorno). Esto es lo que nos interesa, para poder caer en “tell_me_a_secret”. ¿Cómo llegamos a este OFFSET? Analicemos el código: primero, la función lee con una llamada a scanf() el valor entero introducido como parámetro al programa y lo almacena en local_10h. Luego, hace esta comparación 9>local_10h< 99999. El valor 99999 es simplemente la conversión del hexadecimal 0x1869f a decimal (0x1869f = 99999).

Comparando el valor introducido en check_age().

Si local_10h es mayor que 9 y menor que 99999, el flujo del programa cae en el siguiente OFFSET donde la “magia” ocurre:

Comprobación de la parte baja del valor entero introducido.

Se guarda nuestro valor entero en el registro de 32 bits EAX. Después, los 16 bits más bajos de dicho registro (representado por AX) se guardan en la variable local_ah. Así que ahora la variable local_ah contiene los 16 bits menos significativos de nuestro valor entero de 32 bits introducido como parámetro de check_age(). Después, estos 16 bits son guardados en EAX, con los 16 bits altos puestos a 0 (esto es lo que hace la instrucción movzx). Finalmente, para caer en mov eax, 1, que es lo que nos interesa, se comprueba si local_ah es 0. Esta variable sólo será 0 cuando los 16 bits menos significativos de nuestro valor entero sean 0. Por ejemplo, con 0 (0x0000) serían 0. Pero debemos tener un número mayor que 9, así que 0 queda descartado. Si tomamos la representación hexadecimal de un número, cada una de sus cifras representan 4 bits. Por tanto, construimos el siguiente valor hexadecimal: 0x10000. Los últimos 4 ceros son los 16 bits bajos puestos a 0. Si convertimos este número a decimal, obtenemos 65536. Este es el número que hará que la función check_age() retorne 1, en lugar de 0, tal y como se muestra en el código más arriba.

Tell_me_a_secret()

Retornando al código desensamblado de la función main(), después de la llamada a check_age() y únicamente si ésta retorna 1, se hará la llamada a la función tell_me_a_secret(). Si desensamblamos esta función con pdf y su OFFFSET (pdf @ sym.tell_me_a_secret), observamos el código siguiente:

El código desemsamblado de tell_me_a_secret()

Esta función imprime algo mediante printf() y después espera la introducción de datos por parte del usuario con una llamada a scanf(). Recordemos que la convención de llamadas es cdecl, con lo que tendremos 2 parámetros pasados a la función scanf() a través del stack en orden inverso, de derecha a izquierda. Por lo que primero se apila la dirección local_10h (puntero dónde almacenar el valor leído del stdin) y luego [ebx-0x1af2] (el argumento format de scanf) en la pila. Local_10h es una variable de esta función, y como tal, está en la pila. Si observamos la información que nos da r2 al principio (prólogo de la función), local_10h se encuentra en la dirección de pila apuntada por el registro EBP-0x10. Recordemos que la pila crece hacia valores más pequeños de memoria y decrece hacia valores más altos.  Recordemos también que el registro EBP define el nuevo contexto de pila para la función correspondiente. Expresiones dentro de dicha función como EBP-OFFSET siempre definirán variables locales a la función, mientras que expresiones como EBP+OFFSET siempre definirán argumentos pasados a esta función.

Una de las cosas buenas de r2 es que permite depurar y simular la ejecución del código (esto último con el lenguaje ESIL). Durante el reversing, uno puede hacer comprobaciones mediante depuración o emulación, para ver si la lectura estática del código ha sido debidamente entendida. Por ejemplo, ¿que valor de formato se pasa a scanf? Podemos verlo mediante depuración. Abrimos r2 con el flag “-d” para depurar. Después ejecutamos el comando aa. Miramos el OFFSET de la función tell_me_a_secret donde se apila el parámetro “format” de scanf, y asignamos ahí un punto de interrupción con el comando db OFFSET. Finalmente, ejecutamos con el comando dc, introducimos nuestro número mágico para saltarnos el primer escollo de check_age(), 65536, y la ejecución del código se interrumpe. Con dr imprimimos el valor que contiene ahora el registro EAX, y con ps imprimimos la cadena que hay en esa dirección. Tal y como podemos ver, el formato es “%s”, así que scanf() leerá tantos caracteres como deseemos introducir, sin  límite:

Sesión de depuración mediante r2.

Buffer Overflow de manual

Por lo visto en el código anterior, la función tell_me_a_secret() leerá tantos caracteres como nos plazca, sin ningún tipo de comprobación de tamaño, sobre el buffer local_10h. Esta variable está ubicada en EBP-0x10. Es decir, 16 bytes respecto de EBP. Por encima hay otra variable local, de 4 bytes, en EBP-0x4 (local_4h). Por lo tanto, el búffer reservado es de 12 bytes. ¿Que pasa si nuestros datos de entrada son superiores a 12 caracteres? Estaremos escribiendo fuera de la variable de búfer local_10h, por supuesto. Aquí tenemos, pues, un Buffer Overflow (BOF) que debemos explotar. En exploiting, lo primero siempre es conseguir que el programa haga un segmentation fault en una dirección de memoria no válida controlada, por ejemplo 0x41414141, para estar seguros de que controlamos el valor del registro EIP. Abrimos una consola y lanzamos el siguiente comando para escribir más de 16 bytes, y vamos incrementando el número de bytes hasta que logramos que el programa genere el error. Empezaremos con 16 bytes:

Provocando el segmentation fault.

Claramente el valor al que apunta EIP no nos sirve de mucho. Deberemos añadir 8 bytes más, teniendo en cuenta que deberemos sobreescribir EBP y RET (la dirección de retorno). Hasta ahora sólo hemos sobreescrito local_10h y local_4h:

Logramos que EIP valga 0x41414141 (AAAA).

Claramente, con 24 bytes logramos controlar el valor que tendrá el registro EIP. Para casos más complejos, es bueno utilizar herramientas como pattern_create de Metasploit, o pwn tools como PEDA, etc, para saber exactamente el tamaño que necesitamos para controlar EIP. Para este caso, agrupamos unos pocos bytes de 4 en 4 (32 bits) usando As, Bs, y Cs y lo probamos manualmente:

EIP = 0x43434343 (o sea, CCCC). Sobreescribimos el valor de retorno después de 20 bytes.

Es lógico; los primeros 16 bytes sobreescriben local_10h y local_4h; llegamos entonces al valor del contexto de pila apuntado por el registro EBP. Sobreescribimos EBP con 0x42424242 (BBBB) y, después, el valor de retorno con 0x43434343 (CCCC). Ahora ya podemos controlar dónde retornará la función tell_me_a_secret. Recordemos que la función sym.a() no es llamada por el código. Recordemos también que recibe un argumento que imprimirá por pantalla. Recordemos también que la convención cdecl define que los argumentos a funciones se pasen por la pila. Entonces, ¿que podemos hacer? Parecería que podríamos:

  • Hacer que tell_me_a_secret retorne a la función a().
  • Que la función a() imprima lo que queramos (dentro, claro, del espacio de memoria virtual del proceso).

El ROP más simple

¿Que nos gustaría que la función a() nos imprimiera? El valor de la flag, por supuesto. Sabemos que la flag se almacena en obj.flag, así que usando r2 podemos obtener la información sobre la dirección de memoria donde se encuentra obj.flag:

Obtenemos la dirección de memoria de obj.flag

El símbolo está marcado como GLOBAL, y se encuentra en el OFFSET 0x084160a0. En el formato ELF, un símbolo marcado como GLOBAL es accesible desde cualquier parte del binario. Esto significa que podemos referenciar (y de-referenciar) obj.flag desde sym.a(). Ahora es cuando debemos montar nuestro particular ROP. Lo que nos hace falta es alterar el stack de tal manera que:

  • Retornemos a sym.a() desde tell_me_a_secret.
  • sym.a() reciba como parámetro el OFFSET 0x084160a0.
  • Después, para ser precisos, deberíamos hacer que sym.a() retornase a una dirección donde el programa no pete, pero para este caso nos da lo mismo.

En resumen, vamos a preparar nuestra propia pila de ejecución. Nos hace falta saber la dirección de la función sym.a(), se lo preguntamos a r2:

Obtención de la dirección de sym.a()

Ya tenemos todo lo necesario para lanzar nuestro payload. La pila de ejecución que montaremos a nuestro antojo seguirá este esquema:

padding + sym.a() + retorno de sym.a() + obj.flag (parámetro sym.a())

Como estamos en arquitectura Intel,debemos recordar que las direcciones se escriben al revés, por lo que sym.a() pasará a ser 0xcd42418 y obj.flag 0xa060418. El retorno puede ser cualquier cosa que se nos antoje, aunque si queremos evitar provocar un segmentation fault podríamos hacer que salte a una dirección controlada dentro del binario (arreglando la pila adecuadamente, claro). Lanzamos nuestro payload:

Nuestro humilde payload modifica la pila de ejecución e imprime el flag.

Lo probamos contra el servidor:

Obtención de la flag en el servidor usando el mismo payload.

Si lanzamos una sesión con gdb, veremos como, tras imprimirnos el valor de la flag, sym.a() retorna a 0x43434343 (CCCC), tal y como hemos indicado en nuestro payload, provocando un segmentation fault:

 

Dolphin singleClick option not working in XFCE4

Preamble

On an OpenSuse Leap GNU/Linux box, Dolphin’s singleClick option to open files and folders does not work if executing the file manager within an XFCE4 session. Instead, the user is forced to double-click every item to open it. Configuring the global KDE input settings does not help. Editing the ~./config/kdeglobals file and adding the SingleClick=true option does not fix the issue either.

Behind the scenes

To fix this issue, first we need to find where the program reads the singleClick option from and when it enables it. We start trying this issue on another GNU/Linux box running Debian Stretch, and we confirm it suffers from the same problem. So we get its sources and its build dependencies too (this way we are going to be able to build our own customised version of Dolphin, if need be):

apt-get source dolphin
apt-get build-dep dolphin

After that, we look for the string “singleClick” using the grep command inside the src directory:

grep -R “singleClick” *|grep false
src/kitemviews/kitemlistcontroller.cpp: m_singleClickActivationEnforced(false),

According to the previous result, there’s an explicit call to the m_singleClickActivationEnforced() method in src/kitemviews/kitemlistcontroller.cpp. This file has its header file counterpart as well, so we start by having a look at the headers and we find this:

   /**
     * If set to true, the signals itemActivated() and itemsActivated() are emitted
     * after a single-click of the left mouse button. If set to false (the default),
     * the setting from style()-&gt;styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) is used.
     */
    void setSingleClickActivationEnforced(bool singleClick);
    bool singleClickActivationEnforced() const;

It seems pretty obvious then. So according to the comment above, Dolphin is disabling the SingleClick option by default (it’s hard-coded in the call m_singleClickActivationEnforced(false) as we’ve seen earlier). We can change the source code and re-build dolphin to make sure it works, although this solution is far from being optimal because at the next package upgrade, this modification will be lost and the double click issue will re-appear. But we can try it anyway just for fun!

Building our own Dolphin binary with SingleClick enabled by default

We edit the src/kitemviews/kitemlistcontroller.cpp and replace the call to the method m_singleClickActivationEnforced(false) with m_singleClickActivationEnforced(true). Then, we build the binary:

fakeroot debian/rules binary

Once the building process is over, we install all the built packages:

mkdir ../debs ; mv ../*.deb ../debs; su -c “dpkg -i -R ../debs”

If we run the command “dolphin” now, we will have the SingleClick option enabled. So far so good, but … as we have previoisly stated, at the next package upgrade this customisation will be lost.

QT5 Styles without KDE integration

According to the source comment we have previously found, style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) is used in order to enable or disable the SingleClick option within Dolphin. If we google this we find that this is a QT5 Style setting (see http://doc.qt.io/qt-5/qstyle.html) , and it can be easily manipulated with the Qt5ct tool. We do not have this tool as a package for Debian stable, but we do have it for OpenSUSE Leap. So on the computer where this issue was first reported we use Yast2 to install the qt5ct package. Then, we add the following line to the .bashrc file of the affected user:

export QT_QPA_PLATFORMTHEME=qt5ct

Finally, we run the qt5ct tool and we tick the “Activate item on single-click” option on the “Interface” tab, as shown below:

After making this change, we run Dolphin again and this time we have the SingleClick option enabled all right!

 

Una al Mes: Silicon Valley Episodio 1 CTF Write-Up

1: Obtención del archivo con las credenciales.

Descargamos una imagen del disco de Gilfoyle desde cualquiera de los dos enlaces propuestos. Rápidamente pensamos en utilizar Volatilty para analizar dicha imagen. Primero descomprimimos el archivo descargado y luego pedimos a Volatility que nos dé información sobre la imagen, para poder escoger el mejor perfil a utilizar:

 

Obtención de información sobre la imagen del disco de Gilfoyle.

Según el enunciado del reto, debemos encontrar un archivo con las credenciales (encriptadas) de acceso a la web http://34.247.69.86/siliconvalley/episodio1/login.php . Como se comenta que se ha producido un corte eléctrico, podríamos pensar que tal vez ese documento estuviera en uso en el momento del corte. Así que empezamos enumerando procesos que estuvieran en ejecución para luego filtrar aquellos que puedan ser más interesantes (navegadores web, procesadores de texto, programas de anillo de claves, etc.). Para ello usamos Volatility con el primer perfil propuesto en el comando anterior:

volatility -f GILFOYLE-HELLDD.raw –profile=Win7SP1x64 pslist

Entre todos los procesos listados, observamos que LibreOffice y Firefox estaban en ejecución:

Procesos interesantes ejecutados por el usuario.

Empezamos con LibreOffice; usamos de nuevo Volatility para listar aquellos archivos abiertos por el proceso con PID 2340 que estuvieran ubicados en el directorio del usuario (Users). El primero de la lista parece muy prometedor: info.odt:

Volcamos el archivo info.odt a disco para analizarlo con mayor detenimiento:

Volcado del archivo info.odt a disco.

Abrimos el documento con LibreOffice y observamos que, efectivamente, podría muy bien ser el documento con las credenciales cifradas.

2: Descifrando las credenciales

A priori, el contenido del documento parecer utilizar el abecedario de Base-64. Intentamos decodificarlo, pero sin éxito. Lo que sorprende es que el documento parece estar segmentado en grandes bloques de texto independientes, y se puede ver claramente una repetición de dichos bloques. En una parte del documento observamos una separación de párrafo después del carácter “:”. Justo antes y después de dicho caracter observamos lo que podría ser un par de MD5s encerrados entre corchetes:

Posibles MD5s encerrados entre corchetes.

Lo que obtenemos bien podría ser el usuario y el password generado con MD5 que estamos buscando. Probamos suerte directamente pegando estos valores en la web http://md5decrypt.net:

Rompiendo el MD5 (usuario de la web).

Rompiendo el MD5 (password de la web).

Ya tenemos las credenciales; podemos validarnos en la web y proseguir con el reto.

3: Obtención del número de la casa

Tras validarnos en la web con las credenciales Gilfoyle:Satan, obtenemos una nueva URL:

Nueva URL tras validarnos con éxito en la web.

Accedemos a la misma y obtenemos una imagen JPG:

La nueva URL nos lleva hasta una imagen JPEG.

Descargamos la imagen a disco. Antes de proceder con técnicas de stego, obtenemos toda la meta-información posible sobre la imagen. Nos da por pensar: “ey, esto parece una fotografía tomada del archivo de denuncia, tal vez tengamos suerte y tengamos la geolocalización de la cámara donde se tomó la misma…”; así que nos centramos en el campo XMP/IPTC de la imagen y buscamos la localización:

Copiamos y pegamos estas coordenadas en Google Maps. En lugar de un número de casa, Google nos devuelve un código Plus (https://plus.codes/):

Casi lo tenemos; en lugar de un número, Google nos devuelve un Plus Code.

Basta con pulsar sobre la casa que está en la calle indicada por el código Google Plus para obtener su número, el 2126:

Ya tenemos el posible número de la casa, el 2126.

Generamos el MD5 de dicho número:

echo -n “2126”|md5sum
3b92d18aa7a6176dd37d372bc2f1eb71 –

La flag es, pues: UAM{3b92d18aa7a6176dd37d372bc2f1eb71}

 

@disbauxes

Toni Castillo Girona