WWW2Exec - .dtors & .fini_array
.dtors
¡Hoy en día es muy raro encontrar un binario con una sección .dtors!
Los destructores son funciones que se ejecutan antes de que el programa termine (después de que la función main
retorne).
Las direcciones de estas funciones se almacenan dentro de la sección .dtors
del binario y, por lo tanto, si logras escribir la dirección de un shellcode en __DTOR_END__
, este se ejecutará antes de que el programa termine.
Obtén la dirección de esta sección con:
Por lo general, encontrarás los marcadores DTOR entre los valores ffffffff
y 00000000
. Entonces, si solo ves esos valores, significa que no hay ninguna función registrada. Por lo tanto, sobrescribe el 00000000
con la dirección del código shell para ejecutarlo.
Por supuesto, primero necesitas encontrar un lugar para almacenar el código shell para luego llamarlo.
.fini_array
Básicamente, esta es una estructura con funciones que se llamarán antes de que el programa finalice, como .dtors
. Esto es interesante si puedes llamar tu código shell simplemente saltando a una dirección, o en casos en los que necesitas volver a main
nuevamente para explotar la vulnerabilidad por segunda vez.
Ten en cuenta que cuando se ejecuta una función de .fini_array
, se mueve a la siguiente, por lo que no se ejecutará varias veces (evitando bucles eternos), pero también solo te dará una ejecución de la función colocada aquí.
Ten en cuenta que las entradas en .fini_array
se llaman en orden inverso, por lo que probablemente quieras comenzar a escribir desde la última.
Bucle eterno
Para abusar de .fini_array
y obtener un bucle eterno, puedes ver lo que se hizo aquí: Si tienes al menos 2 entradas en .fini_array
, puedes:
Utilizar tu primera escritura para llamar nuevamente a la función de escritura arbitraria vulnerable
Luego, calcular la dirección de retorno en la pila almacenada por
__libc_csu_fini
(la función que llama a todas las funciones de.fini_array
) y colocar allí la dirección de__libc_csu_fini
Esto hará que
__libc_csu_fini
se llame a sí mismo nuevamente ejecutando las funciones de.fini_array
nuevamente, lo que llamará a la función WWW vulnerable 2 veces: una para escritura arbitraria y otra para sobrescribir nuevamente la dirección de retorno de__libc_csu_fini
en la pila para llamarse a sí mismo nuevamente.
Ten en cuenta que con Full RELRO, la sección .fini_array
se vuelve de solo lectura.
link_map
Como se explica en esta publicación, si el programa sale usando return
o exit()
, se ejecutará __run_exit_handlers()
, que llamará a los destructores registrados.
Si el programa sale a través de la función _exit()
, llamará a la llamada al sistema exit
y los manejadores de salida no se ejecutarán. Por lo tanto, para confirmar que se ejecuta __run_exit_handlers()
, puedes establecer un punto de interrupción en él.
El código importante es (fuente):
Note cómo map -> l_addr + fini_array -> d_un.d_ptr
se utiliza para calcular la posición del array de funciones a llamar.
Hay un par de opciones:
Sobrescribir el valor de
map->l_addr
para que apunte a unfini_array
falso con instrucciones para ejecutar código arbitrario.Sobrescribir las entradas
l_info[DT_FINI_ARRAY]
yl_info[DT_FINI_ARRAYSZ]
(que son más o menos consecutivas en memoria), para que apunten a una estructuraElf64_Dyn
falsificada que hará que nuevamentearray
apunte a una zona de memoria controlada por el atacante.Este análisis sobrescribe
l_info[DT_FINI_ARRAY]
con la dirección de una memoria controlada en.bss
que contiene unfini_array
falso. Este array falso contiene primero una dirección de one gadget que se ejecutará y luego la diferencia entre la dirección de este array falso y el valor demap->l_addr
para que*array
apunte al array falso.Según la publicación principal de esta técnica y este análisis ld.so deja un puntero en la pila que apunta al
link_map
binario en ld.so. Con una escritura arbitraria es posible sobrescribirlo y hacer que apunte a unfini_array
falso controlado por el atacante con la dirección de un one gadget por ejemplo.
Siguiendo el código anterior, puedes encontrar otra sección interesante con el código:
En este caso sería posible sobrescribir el valor de map->l_info[DT_FINI]
apuntando a una estructura ElfW(Dyn)
falsificada. Encuentra más información aquí.
Sobrescritura de dtor_list de almacenamiento TLS en __run_exit_handlers
__run_exit_handlers
Como se explica aquí, si un programa sale a través de return
o exit()
, ejecutará __run_exit_handlers()
que llamará a cualquier función destructora registrada.
Código de _run_exit_handlers()
:
Código de __call_tls_dtors()
:
Para cada función registrada en tls_dtor_list
, se desmagnetizará el puntero de cur->func
y se llamará con el argumento cur->obj
.
Usando la función tls
de este fork de GEF, es posible ver que en realidad la lista de dtor_list
está muy cerca del canario de pila y la cookie PTR_MANGLE. Por lo tanto, con un desbordamiento en esto, sería posible sobrescribir la cookie y el canario de pila.
Al sobrescribir la cookie PTR_MANGLE, sería posible burlar la función PTR_DEMANLE
al establecerla en 0x00, lo que significaría que el xor
utilizado para obtener la dirección real es simplemente la dirección configurada. Luego, escribiendo en la lista de dtor_list
es posible encadenar varias funciones con la dirección de la función y su argumento.
Finalmente, hay que tener en cuenta que el puntero almacenado no solo se xorará con la cookie, sino que también se rotará 17 bits:
Por lo tanto, necesitas tener en cuenta esto antes de agregar una nueva dirección.
Encuentra un ejemplo en el post original.
Otros punteros manipulados en __run_exit_handlers
__run_exit_handlers
Esta técnica se explica aquí y depende nuevamente de que el programa salga llamando a return
o exit()
para que se llame a __run_exit_handlers()
.
Veamos más código de esta función:
La variable f
apunta a la estructura initial
y dependiendo del valor de f->flavor
se llamarán diferentes funciones.
Según el valor, la dirección de la función a llamar estará en un lugar diferente, pero siempre estará desenmascarada.
Además, en las opciones ef_on
y ef_cxa
también es posible controlar un argumento.
Es posible verificar la estructura initial
en una sesión de depuración con GEF ejecutando gef> p initial
.
Para abusar de esto, necesitas filtrar o borrar la cookie PTR_MANGLE
y luego sobrescribir una entrada cxa
en initial con system('/bin/sh')
.
Puedes encontrar un ejemplo de esto en el post original del blog sobre la técnica.
Última actualización