Tras haber visto el funcionamiento de las estructuras básicas con las que trabaja malloc (chunks), vamos a estudiar la técnica House of Force.
En las versiones de GLIBC inferiores a la 2.29, el tamaño del top chunk no está sujeto a ninguna comprobación de integridad durante las alocaciones. Esto es fundamental para poder llevar a cabo la explotación, ya que, la idea básica del House of Force es modificar el tamaño del top chunk para poder alocar memoria suficiente como para que la siguiente llamada a malloc sobrescriba nuestro objetivo.
Para poder ver todo esto mejor, vamos a resolver un reto de un CTF reciente:
Use the Force, Luke del Space Heroes CTF 2022. Podéis descargarlo aquí
$ file force force: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ./.glibc/glibc_2.28_no-tcache/ld.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=c50b5c7f0a7dc45dd3409c7fbf1350c534c52662, not stripped
$ checksec force [*] '/home/diegoaltf4/Archivos/force/force' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) RUNPATH: b'./.glibc/glibc_2.28_no-tcache'
Tenemos un binario de 64 bits, dinámicamente enlazado. Además, tiene canarios y NX activado. Si miramos, aparte del binario, tenemos una carpeta .glibc, cuyo contenido es:
Observando las librerías compartidas del binario con el comando ldd, podemos ver que usa la versión 2.28 sin tcache de GLIBC. Como es una versión inferior a la 2.29, el tamaño del top chunk no está sujeto a ninguna comprobación de integridad durante las alocaciones.
$ ldd force linux-vdso.so.1 (0x00007fffdfb5c000) libc.so.6 => ./.glibc/glibc_2.28_no-tcache/libc.so.6 (0x00007fe966f61000) ./.glibc/glibc_2.28_no-tcache/ld.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fe967318000)
Vamos a analizar el decompilado de la función main:
Inicialmente, se imprime la dirección de system y la dirección base de Heap. Para poder mostrar la base de Heap, aloca un chunk que luego libera y resta 0x10 del puntero que devuelve la llamada a malloc.
Se nos muestra un menú y tenemos dos opciones:
(1) Reach out with the force (2) Surrender
Si seleccionamos la primera opción, nos pide dos valores y si seleccionamos la segunda opción salimos del programa. Vamos a analizar la primera opción:
El primer valor que pide es el tamaño que aloca malloc y el segundo el contenido. Si nos fijamos, hace una llamada a malloc_usable_size y luego, a través de read, escribe en el chunk el tamaño que devuelve malloc_usable_size + 8. Ahí tenemos el overflow que nos va a permitir sobrescribir el top chunk. Vamos a comprobarlo:
Si lanzamos el exploit y depuramos con GDB, podemos ver que hemos sobrescrito adecuadamente el top_chunk:
Bien, ahora tenemos que ver qué queremos sobrescribir y calcular la distancia. Dado que este programa realiza llamadas a malloc, podemos intentar controlar el malloc_hook para que apunte a system y podamos obtener una instancia de /bin/sh al alocar un chunk.
Definición de malloc_hook:
The GNU C library lets you modify the behavior of malloc(3), realloc(3), and free(3) by specifying appropriate hook functions. You can use these hooks to help you debug programs that use dynamic memory allocation, for example.
The variable __malloc_initialize_hook points at a function that is called once when the malloc implementation is initialized. This is a weak variable, so it can be overridden in the application with a definition like the following:void (*__malloc_initialize_hook)(void) = my_init_hook;
Para más información, podéis consultar la man page.
La función malloc_hook se encuentra en la GLIBC. Como tenemos un leak de system, podemos calcular la base y a partir de ella, obtener la dirección de malloc_hook.
Ahora necesitamos calcular la distancia a la que nos encontramos del malloc_hook. Siguiendo el diagrama de VA del anterior artículo, podemos ver que GLIBC se encuentra en direcciones más altas que el Heap. Por tanto, la distancia hasta el malloc_hook la podemos calcular de la siguiente forma:
size = (libc.symbols.__malloc_hook - 16) - (heap_leak + 16 + 24)
Nos situamos 16 bytes antes de la dirección de malloc_hook para contemplar el campo size (8 bytes) de este chunk y para tener en cuenta el size del siguiente chunk. Además, a esa dirección le tenemos que restar nuestra posición actual que es la base de heap + los 24 bytes de datos + los 16 bytes de la diferencia del puntero de malloc.
Por tanto, teniendo el tamaño calculado, solo nos queda reservar el chunk. Podemos hacer una prueba para ver si hemos hecho los cálculos bien y si estamos sobrescribiendo el malloc_hook. Para ello, llamamos a la función new_chunk con el tamaño calculado y reservamos un nuevo chunk que tenga un tamaño de 24 bytes y como datos, la dirección de prueba:
size = (libc.symbols.__malloc_hook - 16) - (heap_leak + 16 + 24) new_chunk(str(size).encode(), b'A') new_chunk(b'24', p64(0xdeadbeef))
Como se puede ver, el malloc_hook está apuntando a la dirección que hemos especificado, por lo que el cálculo del tamaño es correcto.
Ahora podemos poner la dirección de system y llamar a malloc pasándole como size el puntero a bin/sh.
Exploit final:
Referencias:
http://phrack.org/issues/66/10.html
https://heap-exploitation.dhavalkapil.com/attacks/house_of_force
https://www.youtube.com/watch?v=s-GJ-buCGio
Espero que os haya gustado y, lo más importante, que hayáis aprendido.
DiegoAltF4
Inicio
Deja una respuesta