Latest Tweets

The libvirtd nightmare

Preamble

Coming from outdated OpenVZ containers, one would surely think that migrating every one of those old containers to virtual machines with native and mainstream Linux Kernel KVM technology would be stable, easy to manage and absolutely not prone to corruption, kernel panics and the like.

Dream on.

The chosen setup: one powerful server with lots of RAM and hard-disk space running on Debian Stretch with an AMD Ryzen 7 processor having native SVM and IOMMU support, native KVM support coming right out from the stable Kernel shipped with Debian out-of-the-box, and libvirtd to make things easier to manage. So far so good, we managed (easily) to migrate the old-fashioned OpenVZ containers to native KVM machines, and we tested them successfully using qemu-system. After that, some trivial stuff like using virt-install to install them and virsh list, virsh start and virsh autostart commands to integrate them within the libvirtd daemon. And, by the way, every single VM running as a non-privileged user.

All of the VMs were running like a charm and smoothly thanks to their bare-metal fast solid state disk. Stable and reliable.

Dream on.

And then, all the hell broke loose

After some time with all the VMs running with no issues at all, I logged myself to the host server using the non-privileged account the VMs were running as. Everything looked normal: the total amount of physical RAM used, the pagination and so on. Then, by the heck of me, I escalated privileges (I performed a su command) to perform some basic sysadmin stuff on the box. At some point, instead of returning to the non-privileged shell with <CTRL+d>, exit or whatever, I did this:

su – kvmuser

Then, magically, another instance of every single VM started up all at once, for all those VMs marked as “autostart” by libvirtd. So in no time at all, all the VMs started complaining about data integrity and instability on their file systems. No wonder why, because we had two instances of qemu-system per VM, accessing the very same qcow2 image! In no time at all, all the filesystems were mounted read-only for the first VM instances. At some point, funny things started to happen and data corruption became the norm. Chaos aplenty. Data loss and an incredible headache as I have never experienced in my whole life as a Sysadmin.

What the fuck man?

What is wrong with libvirtd

Once the VMs were restored and everything was again under perfect control, I did some research and tests. Of course we had two main problems here, the first one being not having a locking system preventing the VMs from accesing read/write the very same image twice. Heck, as far as I’m concerned, there are different ways to implement that. The second issue here was, of course, why on earth libvirtd was starting the already running VMs twice.

On another box mirroring the exact same setup, I logged as kvmuser, listed the running VMs, then became root and performed the su Рkvmuser command and ran virsh list again to list the running VMs. Instead of getting the same ones, I got a suspicious and buggy empty list! See and marvel:

kvmuser@kvmbox:~$ virsh list
Id Name State
—————————————————-
1 Test running

kvmuser@kvmbox:~$ su
Password:
root@kvmbox:/home/kvmuser# su – kvmuser
kvmuser@kvmbox:~$ virsh list
Id Name State
—————————————————-

So that was it. The second virsh list command was not seeing the same VM “Test” running, although with a virst list –all it did see all the installed ones alright.

Tracing the socket

The virsh command connects to the libvirtd daemon, of course. And libvirtd is listening for connections from clients on a socket. Unless you tell libvirtd otherwise, it listens to connections on a UNIX socket. So I used strace to determine if the virsh command was using the same socket, something I suspected was not the case. The first time, when virsh was correctly seeing the “Test” VM running, this is what I got:

connect(5, {sa_family=AF_UNIX, sun_path=”/var/run/nscd/socket”}, 110) = -1 ENOENT (No such file or directory)
connect(5, {sa_family=AF_UNIX, sun_path=”/var/run/nscd/socket”}, 110) = -1 ENOENT (No such file or directory)
connect(6, {sa_family=AF_UNIX, sun_path=”/run/user/1001/libvirt/libvirt-sock”}, 110) = 0

So the socket was /run/user/1001/libvirt/libvirt-sock. Then, I became root and then I performed the su Рkvmuser again, and I traced the second virsh list command:

connect(5, {sa_family=AF_UNIX, sun_path=”/var/run/nscd/socket”}, 110) = -1 ENOENT (No such file or directory)
connect(5, {sa_family=AF_UNIX, sun_path=”/var/run/nscd/socket”}, 110) = -1 ENOENT (No such file or directory)
connect(6, {sa_family=AF_UNIX, sun_path=”/home/kvmuser/.cache/libvirt/libvirt-sock”}, 110) = 0

The second time, the UNIX socket used was a different one, which explained why the virsh command did not see the same running VMs as the first one!

A trivial fix

According to libvirtd manpage:

$XDG_RUNTIME_DIR/libvirt/libvirt-sock
The socket libvirtd will use.

If $XDG_RUNTIME_DIR is not set in your environment, libvirtd will use $HOME/.cache

So, the first time, $XDG_RUNTIME_DIR was correctly set, and thus the socket was created on the /run/user/UID/ directory. After executing su and then su Рkmvuser, though, this variable was not set at all, therefore libvirtd was using the UNIX socket under /home/$USER/.cache, as shown above.

This absolutely trivial thing rendered all of our KVMs corrupt in a matter of seconds. To fix it, I added the following line to the .bashrc file of the “kvmuser” user:

export XDG_RUNTIME_DIR=/run/user/$UID

And now, everything (but locking) works like a charm.

So far.

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.deb.¬†Esto 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!