WWW2Exec - atexit(), TLS Storage & Other mangled Pointers

Reading time: 9 minutes

tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks

__atexit Structures

caution

Σήμερα είναι πολύ περίεργο να εκμεταλλευτείς αυτό!

atexit() είναι μια συνάρτηση στην οποία άλλες συναρτήσεις περνιούνται ως παράμετροι. Αυτές οι συναρτήσεις θα εκτελούνται κατά την εκτέλεση μιας exit() ή της επιστροφής της κύριας.
Αν μπορείς να τροποποιήσεις τη διεύθυνση οποιασδήποτε από αυτές τις συναρτήσεις ώστε να δείχνει σε ένα shellcode για παράδειγμα, θα κερδίσεις έλεγχο της διαδικασίας, αλλά αυτό είναι αυτή τη στιγμή πιο περίπλοκο.
Αυτή τη στιγμή οι διευθύνσεις στις συναρτήσεις που θα εκτελούνται είναι κρυμμένες πίσω από πολλές δομές και τελικά η διεύθυνση στην οποία δείχνει δεν είναι οι διευθύνσεις των συναρτήσεων, αλλά είναι κρυπτογραφημένες με XOR και μετατοπίσεις με μια τυχαία κλειδί. Έτσι, αυτή τη στιγμή αυτός ο επιθετικός παράγοντας είναι όχι πολύ χρήσιμος τουλάχιστον σε x86 και x64_86.
Η συνάρτηση κρυπτογράφησης είναι PTR_MANGLE. Άλλες αρχιτεκτονικές όπως m68k, mips32, mips64, aarch64, arm, hppa... δεν υλοποιούν τη συνάρτηση κρυπτογράφησης γιατί επιστρέφει το ίδιο με αυτό που έλαβε ως είσοδο. Έτσι, αυτές οι αρχιτεκτονικές θα μπορούσαν να επιτεθούν μέσω αυτού του παραδείγματος.

Μπορείς να βρεις μια σε βάθος εξήγηση για το πώς λειτουργεί αυτό στο https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html

Όπως εξηγήθηκε σε αυτή την ανάρτηση, Αν το πρόγραμμα τερματίσει χρησιμοποιώντας return ή exit() θα εκτελέσει __run_exit_handlers() που θα καλέσει τις καταχωρημένες καταστροφές.

caution

Αν το πρόγραμμα τερματίσει μέσω της _exit() συνάρτησης, θα καλέσει την exit syscall και οι χειριστές εξόδου δεν θα εκτελούνται. Έτσι, για να επιβεβαιώσεις ότι εκτελείται το __run_exit_handlers(), μπορείς να ορίσεις ένα breakpoint σε αυτό.

Ο σημαντικός κώδικας είναι (source):

c
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
}

Σημειώστε πώς το map -> l_addr + fini_array -> d_un.d_ptr χρησιμοποιείται για να υπολογίσει τη θέση του πίνακα συναρτήσεων που θα καλέσουμε.

Υπάρχουν μερικές επιλογές:

  • Επαναγράψτε την τιμή του map->l_addr για να δείχνει σε ένα ψεύτικο fini_array με οδηγίες για την εκτέλεση αυθαίρετου κώδικα.
  • Επαναγράψτε τις εγγραφές l_info[DT_FINI_ARRAY] και l_info[DT_FINI_ARRAYSZ] (οι οποίες είναι περισσότερο ή λιγότερο διαδοχικές στη μνήμη), ώστε να δείχνουν σε μια κατασκευασμένη δομή Elf64_Dyn που θα κάνει ξανά τον array να δείχνει σε μια ζώνη μνήμης που ελέγχεται από τον επιτιθέμενο.
  • Αυτή η αναφορά επαναγράφει το l_info[DT_FINI_ARRAY] με τη διεύθυνση μιας ελεγχόμενης μνήμης στο .bss που περιέχει ένα ψεύτικο fini_array. Αυτός ο ψεύτικος πίνακας περιέχει πρώτα μια διεύθυνση one gadget που θα εκτελεστεί και στη συνέχεια τη διαφορά μεταξύ της διεύθυνσης αυτού του ψεύτικου πίνακα και της τιμής του map->l_addr ώστε το *array να δείχνει στον ψεύτικο πίνακα.
  • Σύμφωνα με την κύρια ανάρτηση αυτής της τεχνικής και αυτή την αναφορά το ld.so αφήνει έναν δείκτη στη στοίβα που δείχνει στον δυαδικό link_map στο ld.so. Με μια αυθαίρετη εγγραφή είναι δυνατό να τον επαναγράψετε και να τον κάνετε να δείχνει σε ένα ψεύτικο fini_array που ελέγχεται από τον επιτιθέμενο με τη διεύθυνση ενός one gadget για παράδειγμα.

Ακολουθώντας τον προηγούμενο κώδικα μπορείτε να βρείτε μια άλλη ενδιαφέρουσα ενότητα με τον κώδικα:

c
/* 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));
}

Σε αυτή την περίπτωση, θα ήταν δυνατό να επαναγραφεί η τιμή του map->l_info[DT_FINI] που δείχνει σε μια πλαστή δομή ElfW(Dyn). Βρείτε περισσότερες πληροφορίες εδώ.

TLS-Storage dtor_list επαναγραφή στο __run_exit_handlers

Όπως εξηγείται εδώ, αν ένα πρόγραμμα τερματίσει μέσω return ή exit(), θα εκτελέσει __run_exit_handlers() το οποίο θα καλέσει οποιαδήποτε συνάρτηση καταστροφέα έχει καταχωρηθεί.

Κώδικας από _run_exit_handlers():

c
/* 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 ();

Κώδικας από __call_tls_dtors():

c
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);
[...]
}
}

Για κάθε καταχωρημένη συνάρτηση στη tls_dtor_list, θα αποσυμπιέσει τον δείκτη από το cur->func και θα την καλέσει με το επιχείρημα cur->obj.

Χρησιμοποιώντας τη συνάρτηση tls από αυτό το fork του GEF, είναι δυνατόν να δούμε ότι στην πραγματικότητα η dtor_list είναι πολύ κοντά στο stack canary και το PTR_MANGLE cookie. Έτσι, με μια υπερχείλιση σε αυτό, θα ήταν δυνατό να επικαλυφθεί το cookie και το stack canary.
Επικαλύπτοντας το PTR_MANGLE cookie, θα ήταν δυνατό να παρακαμφθεί η συνάρτηση PTR_DEMANLE ρυθμίζοντάς την σε 0x00, που σημαίνει ότι το xor που χρησιμοποιείται για να αποκτήσει τη πραγματική διεύθυνση είναι απλώς η διεύθυνση που έχει ρυθμιστεί. Στη συνέχεια, γράφοντας στη dtor_list είναι δυνατό να αλυσιδωθούν πολλές συναρτήσεις με τη διεύθυνση της συνάρτησης και το επιχείρημά της.

Τέλος, σημειώστε ότι ο αποθηκευμένος δείκτης δεν θα xored μόνο με το cookie αλλά και θα περιστραφεί 17 bits:

armasm
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

Πρέπει να λάβετε υπόψη αυτό πριν προσθέσετε μια νέα διεύθυνση.

Βρείτε ένα παράδειγμα στην αρχική ανάρτηση.

Άλλοι παραμορφωμένοι δείκτες στο __run_exit_handlers

Αυτή η τεχνική είναι εξηγημένη εδώ και εξαρτάται ξανά από το πρόγραμμα να τερματίζει καλώντας return ή exit() ώστε να κληθεί __run_exit_handlers().

Ας ελέγξουμε περισσότερος κώδικας αυτής της συνάρτησης:

c
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);

Η μεταβλητή f δείχνει στη initial δομή και ανάλογα με την τιμή του f->flavor θα κληθούν διαφορετικές συναρτήσεις.
Ανάλογα με την τιμή, η διεύθυνση της συνάρτησης που θα κληθεί θα είναι σε διαφορετική θέση, αλλά θα είναι πάντα demangled.

Επιπλέον, στις επιλογές ef_on και ef_cxa είναι επίσης δυνατός ο έλεγχος ενός argument.

Είναι δυνατόν να ελέγξετε τη initial δομή σε μια συνεδρία αποσφαλμάτωσης με το GEF να τρέχει gef> p initial.

Για να εκμεταλλευτείτε αυτό, χρειάζεται είτε να leak ή να διαγράψετε το PTR_MANGLE cookie και στη συνέχεια να αντικαταστήσετε μια είσοδο cxa στην αρχική με system('/bin/sh').
Μπορείτε να βρείτε ένα παράδειγμα αυτού στο original blog post about the technique.

tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks