WWW2Exec - atexit(), TLS Storage & Altri puntatori danneggiati
Reading time: 8 minutes
tip
Impara e pratica l'Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica l'Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos di github.
__atexit Structures
caution
Oggigiorno è molto strano sfruttare questo!
atexit()
è una funzione a cui altre funzioni vengono passate come parametri. Queste funzioni verranno eseguite quando si esegue un exit()
o il ritorno del main.
Se puoi modificare l'indirizzo di una di queste funzioni per puntare a un shellcode, ad esempio, guadagnerai 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 è l'indirizzo delle funzioni, ma è crittografato 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 come input. Quindi queste architetture sarebbero attaccabili tramite questo vettore.
Puoi trovare una spiegazione approfondita su come funziona in https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html
link_map
Come spiegato in questo post, se il programma esce usando return
o exit()
verrà eseguito __run_exit_handlers()
che chiamerà i distruttori registrati.
caution
Se il programma esce tramite la funzione _exit()
, chiamerà la syscall exit
e i gestori di uscita non verranno eseguiti. Quindi, per confermare che __run_exit_handlers()
venga eseguito, puoi impostare un breakpoint su di esso.
Il codice importante è (source):
ElfW(Dyn) *fini_array = map->l_info[DT_FINI_ARRAY];
if (fini_array != NULL)
{
ElfW(Addr) *array = (ElfW(Addr) *) (map->l_addr + fini_array->d_un.d_ptr);
size_t sz = (map->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr)));
while (sz-- > 0)
((fini_t) array[sz]) ();
}
[...]
// This is the d_un structure
ptype l->l_info[DT_FINI_ARRAY]->d_un
type = union {
Elf64_Xword d_val; // address of function that will be called, we put our onegadget here
Elf64_Addr d_ptr; // offset from l->l_addr of our structure
}
Nota come map -> l_addr + fini_array -> d_un.d_ptr
venga utilizzato per calcolare la posizione dell'array di funzioni da chiamare.
Ci sono un paio di opzioni:
- Sovrascrivere il valore di
map->l_addr
per farlo puntare a un falsofini_array
con istruzioni per eseguire codice arbitrario. - Sovrascrivere le voci
l_info[DT_FINI_ARRAY]
el_info[DT_FINI_ARRAYSZ]
(che sono più o meno consecutive in memoria), per farle puntare a una strutturaElf64_Dyn
contraffatta che farà nuovamente puntarearray
a una zona di memoria controllata dall'attaccante. - Questo writeup sovrascrive
l_info[DT_FINI_ARRAY]
con l'indirizzo di una memoria controllata in.bss
contenente un falsofini_array
. Questo falso array contiene prima un one gadget indirizzo che verrà eseguito e poi la differenza tra l'indirizzo di questo falso array e il valore dimap->l_addr
in modo che*array
punti al falso array. - Secondo il post principale di questa tecnica e questo writeup 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 falsofini_array
controllato dall'attaccante con l'indirizzo di un one gadget per esempio.
Seguendo il codice precedente puoi trovare un'altra sezione interessante con il codice:
/* Next try the old-style destructor. */
ElfW(Dyn) *fini = map->l_info[DT_FINI];
if (fini != NULL)
DL_CALL_DT_FINI (map, ((void *) map->l_addr + fini->d_un.d_ptr));
}
In questo caso sarebbe possibile sovrascrivere il valore di map->l_info[DT_FINI]
puntando a una struttura ElfW(Dyn)
contraffatta. Trova ulteriori informazioni qui.
Sovrascrittura di dtor_list in __run_exit_handlers
Come spiegato qui, se un programma termina tramite return
o exit()
, eseguirà __run_exit_handlers()
che chiamerà qualsiasi funzione distruttrice registrata.
Codice da _run_exit_handlers()
:
/* Call all functions registered with `atexit' and `on_exit',
in the reverse of the order in which they were registered
perform stdio cleanup, and terminate program execution with STATUS. */
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit, bool run_dtors)
{
/* First, call the TLS destructors. */
#ifndef SHARED
if (&__call_tls_dtors != NULL)
#endif
if (run_dtors)
__call_tls_dtors ();
Codice da __call_tls_dtors()
:
typedef void (*dtor_func) (void *);
struct dtor_list //struct added
{
dtor_func func;
void *obj;
struct link_map *map;
struct dtor_list *next;
};
[...]
/* Call the destructors. This is called either when a thread returns from the
initial function or when the process exits via the exit function. */
void
__call_tls_dtors (void)
{
while (tls_dtor_list) // parse the dtor_list chained structures
{
struct dtor_list *cur = tls_dtor_list; // cur point to tls-storage dtor_list
dtor_func func = cur->func;
PTR_DEMANGLE (func); // demangle the function ptr
tls_dtor_list = tls_dtor_list->next; // next dtor_list structure
func (cur->obj);
[...]
}
}
Per ogni funzione registrata in tls_dtor_list
, verrà demanglato il puntatore da cur->func
e chiamato con l'argomento cur->obj
.
Utilizzando la funzione tls
da questo fork di GEF, è possibile vedere che in realtà la dtor_list
è molto vicina al stack canary e al PTR_MANGLE cookie. Quindi, con un overflow su di essa sarebbe possibile sovrascrivere il cookie e lo stack canary.
Sovrascrivendo il PTR_MANGLE cookie, sarebbe possibile bypassare la funzione PTR_DEMANLE
impostandola a 0x00, il che significa che il xor
utilizzato per ottenere l'indirizzo reale è semplicemente l'indirizzo configurato. Poi, scrivendo sulla dtor_list
è possibile collegare diverse funzioni con l'indirizzo della funzione e il suo argomento.
Infine, nota che il puntatore memorizzato non verrà solo xored con il cookie ma anche ruotato di 17 bit:
0x00007fc390444dd4 <+36>: mov rax,QWORD PTR [rbx] --> mangled ptr
0x00007fc390444dd7 <+39>: ror rax,0x11 --> rotate of 17 bits
0x00007fc390444ddb <+43>: xor rax,QWORD PTR fs:0x30 --> xor with PTR_MANGLE
Quindi devi tenere conto di questo prima di aggiungere un nuovo indirizzo.
Trova un esempio nel post originale.
Altri puntatori danneggiati in __run_exit_handlers
Questa tecnica è spiegata qui e dipende ancora una volta dal programma che termina chiamando return
o exit()
così __run_exit_handlers()
viene chiamato.
Controlliamo più codice di questa funzione:
while (true)
{
struct exit_function_list *cur;
restart:
cur = *listp;
if (cur == NULL)
{
/* Exit processing complete. We will not allow any more
atexit/on_exit registrations. */
__exit_funcs_done = true;
break;
}
while (cur->idx > 0)
{
struct exit_function *const f = &cur->fns[--cur->idx];
const uint64_t new_exitfn_called = __new_exitfn_called;
switch (f->flavor)
{
void (*atfct) (void);
void (*onfct) (int status, void *arg);
void (*cxafct) (void *arg, int status);
void *arg;
case ef_free:
case ef_us:
break;
case ef_on:
onfct = f->func.on.fn;
arg = f->func.on.arg;
PTR_DEMANGLE (onfct);
/* Unlock the list while we call a foreign function. */
__libc_lock_unlock (__exit_funcs_lock);
onfct (status, arg);
__libc_lock_lock (__exit_funcs_lock);
break;
case ef_at:
atfct = f->func.at;
PTR_DEMANGLE (atfct);
/* Unlock the list while we call a foreign function. */
__libc_lock_unlock (__exit_funcs_lock);
atfct ();
__libc_lock_lock (__exit_funcs_lock);
break;
case ef_cxa:
/* To avoid dlclose/exit race calling cxafct twice (BZ 22180),
we must mark this function as ef_free. */
f->flavor = ef_free;
cxafct = f->func.cxa.fn;
arg = f->func.cxa.arg;
PTR_DEMANGLE (cxafct);
/* Unlock the list while we call a foreign function. */
__libc_lock_unlock (__exit_funcs_lock);
cxafct (arg, status);
__libc_lock_lock (__exit_funcs_lock);
break;
}
if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
/* The last exit function, or another thread, has registered
more exit functions. Start the loop over. */
goto restart;
}
*listp = cur->next;
if (*listp != NULL)
/* Don't free the last element in the chain, this is the statically
allocate element. */
free (cur);
}
__libc_lock_unlock (__exit_funcs_lock);
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 in esecuzione gef> p initial
.
Per abusare di questo è necessario leakare o cancellare il PTR_MANGLE
cookie e poi sovrascrivere un'entrata cxa
in initial con system('/bin/sh')
.
Puoi trovare un esempio di questo nel post originale del blog sulla tecnica.
tip
Impara e pratica l'Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica l'Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos di github.