WWW2Exec - atexit()
Last updated
Last updated
Impara e pratica l'Hacking su AWS:HackTricks Training AWS Red Team Expert (ARTE) Impara e pratica l'Hacking su GCP: HackTricks Training GCP Red Team Expert (GRTE)
Oggi è molto strano sfruttare questo!
atexit()
è una funzione a cui vengono passate altre funzioni come parametri. Queste funzioni verranno eseguite quando si esegue un exit()
o il ritorno del main.
Se riesci a modificare l'indirizzo di una di queste funzioni in modo che punti a un codice shell ad esempio, otterrai il controllo del processo, ma attualmente è più complicato.
Attualmente gli indirizzi delle funzioni da eseguire sono nascosti dietro diverse strutture e infine l'indirizzo a cui puntano non sono gli indirizzi delle funzioni, ma sono criptati con XOR e spostamenti con una chiave casuale. Quindi attualmente questo vettore di attacco non è molto utile almeno su x86 e x64_86.
La funzione di crittografia è PTR_MANGLE
. Altre architetture come m68k, mips32, mips64, aarch64, arm, hppa... non implementano la funzione di crittografia perché restituisce lo stesso che ha ricevuto in input. Quindi queste architetture sarebbero attaccabili da questo vettore.
Puoi trovare una spiegazione dettagliata su come funziona questo in https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html
Come spiegato in questo post, Se il programma esce usando return
o exit()
eseguirà __run_exit_handlers()
che chiamerà i distruttori registrati.
Se il programma esce tramite la funzione _exit()
, chiamerà la syscall di exit e i gestori di uscita non verranno eseguiti. Quindi, per confermare che __run_exit_handlers()
viene eseguito, è possibile impostare un breakpoint su di esso.
Il codice importante è (fonte):
Nota come map -> l_addr + fini_array -> d_un.d_ptr
viene utilizzato per calcolare la posizione dell'array di funzioni da chiamare.
Ci sono un paio di opzioni:
Sovrascrivere il valore di map->l_addr
in modo che punti a un falso fini_array
con istruzioni per eseguire codice arbitrario
Sovrascrivere le voci l_info[DT_FINI_ARRAY]
e l_info[DT_FINI_ARRAYSZ]
(che sono più o meno consecutive in memoria), in modo che puntino a una struttura Elf64_Dyn
falsificata che farà di nuovo sì che array
punti a una zona di memoria controllata dall'attaccante.
Questa spiegazione sovrascrive l_info[DT_FINI_ARRAY]
con l'indirizzo di una memoria controllata in .bss
contenente un falso fini_array
. Questo array falso contiene prima un indirizzo one gadget che verrà eseguito e poi la differenza tra l'indirizzo di questo array falso e il valore di map->l_addr
in modo che *array
punti all'array falso.
Secondo il post principale di questa tecnica e questa spiegazione ld.so lascia un puntatore nello stack che punta al link_map
binario in ld.so. Con una scrittura arbitraria è possibile sovrascriverlo e farlo puntare a un falso fini_array
controllato dall'attaccante con l'indirizzo di un one gadget ad esempio.
Segue il codice precedente un'altra sezione interessante con il codice:
In questo caso sarebbe possibile sovrascrivere il valore di map->l_info[DT_FINI]
puntando a una struttura ElfW(Dyn)
forgiata. Trova ulteriori informazioni qui.
__run_exit_handlers
Come spiegato qui, se un programma esce tramite return
o exit()
, eseguirà __run_exit_handlers()
che chiamerà qualsiasi funzione di distruttore registrata.
Codice da _run_exit_handlers()
:
Codice da __call_tls_dtors()
:
Per ogni funzione registrata in tls_dtor_list
, demanglerà il puntatore da cur->func
e lo chiamerà con l'argomento cur->obj
.
Utilizzando la funzione tls
da questo fork di GEF, è possibile vedere che effettivamente la dtor_list
è molto vicina al canary dello stack e al cookie PTR_MANGLE. Quindi, con un overflow su di esso sarebbe possibile sovrascrivere il cookie e il canary dello stack.
Sovrascrivendo il cookie PTR_MANGLE, sarebbe possibile aggirare la funzione PTR_DEMANLE
impostandola su 0x00, ciò significherebbe che lo xor
usato per ottenere l'indirizzo reale è proprio l'indirizzo configurato. Quindi, scrivendo sulla dtor_list
è possibile concatenare diverse funzioni con l'indirizzo della funzione e il suo argomento.
Infine notare che il puntatore memorizzato non solo verrà sottoposto a xor con il cookie ma verrà anche ruotato di 17 bit:
Quindi è necessario tenere conto di questo prima di aggiungere un nuovo indirizzo.
Trova un esempio nel post originale.
__run_exit_handlers
Questa tecnica è spiegata qui e dipende nuovamente dal programma che esce chiamando return
o exit()
quindi viene chiamato __run_exit_handlers()
.
Controlliamo più codice di questa funzione:
La variabile f
punta alla struttura initial
e a seconda del valore di f->flavor
verranno chiamate diverse funzioni.
A seconda del valore, l'indirizzo della funzione da chiamare sarà in un posto diverso, ma sarà sempre demangled.
Inoltre, nelle opzioni ef_on
e ef_cxa
è anche possibile controllare un argomento.
È possibile controllare la struttura initial
in una sessione di debug con GEF eseguendo gef> p initial
.
Per sfruttare ciò è necessario leak o cancellare il cookie PTR_MANGLE
e sovrascrivere una voce cxa
in initial con system('/bin/sh')
.
È possibile trovare un esempio di ciò nel post originale del blog sulla tecnica.