19 KiB
WWW2Exec - .dtors & .fini_array
Μάθετε το χάκινγκ στο AWS από το μηδέν μέχρι τον ήρωα με το htARTE (HackTricks AWS Red Team Expert)!
Άλλοι τρόποι υποστήριξης του HackTricks:
- Αν θέλετε να δείτε την εταιρεία σας διαφημισμένη στο HackTricks ή να κατεβάσετε το HackTricks σε μορφή PDF ελέγξτε τα ΣΧΕΔΙΑ ΣΥΝΔΡΟΜΗΣ!
- Αποκτήστε το επίσημο PEASS & HackTricks swag
- Ανακαλύψτε την Οικογένεια PEASS, τη συλλογή μας από αποκλειστικά NFTs
- Εγγραφείτε στη 💬 ομάδα Discord ή στη ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε τα χάκινγκ κόλπα σας υποβάλλοντας PRs στα HackTricks και HackTricks Cloud αποθετήρια του GitHub.
.dtors
{% hint style="danger" %} Σήμερα είναι πολύ παράξενο να βρείτε ένα δυαδικό με τμήμα .dtors! {% endhint %}
Οι καταστροφείς είναι συναρτήσεις που εκτελούνται πριν το πρόγραμμα τελειώσει (μετά την επιστροφή της συνάρτησης main
).
Οι διευθύνσεις αυτών των συναρτήσεων αποθηκεύονται μέσα στο .dtors
τμήμα του δυαδικού και επομένως, αν καταφέρετε να γράψετε τη διεύθυνση ενός shellcode στο __DTOR_END__
, αυτό θα εκτελεστεί πριν το πρόγραμμα τελειώσει.
Αποκτήστε τη διεύθυνση αυτού του τμήματος με:
objdump -s -j .dtors /exec
rabin -s /exec | grep “__DTOR”
Συνήθως θα βρείτε τα σήματα DTOR μεταξύ των τιμών ffffffff
και 00000000
. Έτσι, αν βλέπετε μόνο αυτές τις τιμές, σημαίνει ότι δεν υπάρχει καμία συνάρτηση που έχει καταχωρηθεί. Επομένως, αντικαταστήστε το 00000000
με τη διεύθυνση του shellcode για να το εκτελέσετε.
{% hint style="warning" %} Φυσικά, πρέπει πρώτα να βρείτε ένα μέρος για να αποθηκεύσετε το shellcode ώστε να το καλέσετε αργότερα. {% endhint %}
.fini_array
Ουσιαστικά αυτή είναι μια δομή με συναρτήσεις που θα κληθούν πριν το πρόγραμμα ολοκληρωθεί, όπως η .dtors
. Αυτό είναι ενδιαφέρον όταν μπορείτε να καλέσετε το shellcode απλά αναπηδώντας σε μια διεύθυνση, ή σε περιπτώσεις όπου χρειάζεται να πάτε πίσω στο main
ξανά για να εκμεταλλευτείτε την ευπάθεια μια δεύτερη φορά.
objdump -s -j .fini_array ./greeting
./greeting: file format elf32-i386
Contents of section .fini_array:
8049934 a0850408
#Put your address in 0x8049934
Σημειώστε ότι όταν εκτελείται μια συνάρτηση από το .fini_array
μετακινείται στην επόμενη, οπότε δεν θα εκτελεστεί πολλές φορές (αποτρέποντας ατέρμονες βρόχους), αλλά θα σας δώσει μόνο 1 εκτέλεση της συνάρτησης που τοποθετήθηκε εδώ.
Σημειώστε ότι οι καταχωρήσεις στο .fini_array
καλούνται με αντίστροφη σειρά, οπότε πιθανόν θέλετε να ξεκινήσετε την εγγραφή από την τελευταία.
Ατέρμονη βρόχος
Για να εκμεταλλευτείτε το .fini_array
για να έχετε έναν ατέρμονο βρόχο μπορείτε να ελέγξετε τι έγινε εδώ: Αν έχετε τουλάχιστον 2 καταχωρήσεις στο .fini_array
, μπορείτε:
- Χρησιμοποιήστε την πρώτη εγγραφή σας για να καλέσετε ξανά την ευπάθεια της αυθαίρετης εγγραφής
- Στη συνέχεια, υπολογίστε τη διεύθυνση επιστροφής στη στοίβα που αποθηκεύεται από το
__libc_csu_fini
(η συνάρτηση που καλεί όλες τις λειτουργίες του.fini_array
) και τοποθετήστε εκεί τη διεύθυνση του__libc_csu_fini
- Αυτό θα κάνει το
__libc_csu_fini
να καλέσει ξανά τον εαυτό του εκτελώντας τις λειτουργίες του.fini_array
ξανά, οι οποίες θα καλέσουν την ευάθροτη WWW συνάρτηση 2 φορές: μία για την αυθαίρετη εγγραφή και μια για να αντικαταστήσει ξανά τη διεύθυνση επιστροφής του__libc_csu_fini
στη στοίβα για να καλέσει ξανά τον εαυτό του.
{% hint style="danger" %}
Σημειώστε ότι με Πλήρη RELRO, η ενότητα .fini_array
γίνεται μόνο για ανάγνωση.
{% endhint %}
link_map
Όπως εξηγείται σε αυτήν την ανάρτηση, Αν το πρόγραμμα τερματίσει χρησιμοποιώντας return
ή exit()
θα εκτελέσει το __run_exit_handlers()
που θα καλέσει τους εγγεγραμμένους καταστροφείς.
{% hint style="danger" %}
Αν το πρόγραμμα τερματίσει μέσω της λειτουργίας _exit()
, θα καλέσει τη κλήση συστήματος exit
και οι χειριστές εξόδου δεν θα εκτελεστούν. Έτσι, για να επιβεβαιώσετε ότι το __run_exit_handlers()
εκτελείται μπορείτε να ορίσετε ένα σημείο διακοπής σε αυτό.
{% endhint %}
Ο σημαντικός κώδικας είναι (πηγή):
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 για παράδειγμα.
Ακολουθώντας τον προηγούμενο κώδικα μπορείτε να βρείτε μια άλλη ενδιαφέρουσα ενότητα με τον κώδικα:
/* 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)
δομή. Βρείτε περισσότερες πληροφορίες εδώ.
Αντικατάσταση της λίστας dtor_list αποθήκευσης TLS στο __run_exit_handlers
Όπως εξηγείται εδώ, αν ένα πρόγραμμα τερματίζει μέσω return
ή exit()
, θα εκτελέσει το __run_exit_handlers()
το οποίο θα καλέσει οποιαδήποτε συνάρτηση καταστροφέων που έχει καταχωρηθεί.
Κώδικας από το _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 ();
Κώδικας από το __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);
[...]
}
}
Για κάθε εγγεγραμμένη λειτουργία στο 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
είναι δυνατό να αλυσιδωθούν αρκετές λειτουργίες με τη διεύθυνση της λειτουργίας και το όρισμά της.
Τέλος, παρατηρήστε ότι ο αποθηκευμένος δείκτης δεν θα υποστεί μόνο λογική πράξη XOR με το cookie αλλά θα περιστραφεί επίσης 17 bits:
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()
.
Ας ελέγξουμε περισσότερο κώδικα αυτής της συνάρτησης:
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
θα κληθούν διαφορετικές συναρτήσεις.
Ανάλογα με την τιμή, η διεύθυνση της συνάρτησης που θα κληθεί θα βρίσκεται σε διαφορετική θέση, αλλά θα είναι πάντα αποκωδικοποιημένη.
Επιπλέον, στις επιλογές ef_on
και ef_cxa
είναι επίσης δυνατόν να ελέγξετε ένα όρισμα.
Είναι δυνατόν να ελέγξετε τη δομή initial
σε μια συνεδρία εντοπισμού σφαλμάτων με το GEF εκτελώντας gef> p initial
.
Για να εκμεταλλευτείτε αυτό, πρέπει είτε να διαρρεύσετε είτε να διαγράψετε το PTR_MANGLE
cookie και στη συνέχεια να αντικαταστήσετε μια είσοδο cxa
στο initial με system('/bin/sh')
.
Μπορείτε να βρείτε ένα παράδειγμα αυτού στην αρχική ανάρτηση στο blog σχετικά με την τεχνική.