WWW2Exec - .dtors & .fini_array

Lernen Sie AWS-Hacking von Grund auf mit htARTE (HackTricks AWS Red Team Expert)!

Andere Möglichkeiten, HackTricks zu unterstützen:

.dtors

Heutzutage ist es sehr seltsam, eine Binärdatei mit einem .dtors-Abschnitt zu finden!

Die Destruktoren sind Funktionen, die ausgeführt werden, bevor das Programm beendet wird (nachdem die main-Funktion zurückkehrt). Die Adressen dieser Funktionen sind im .dtors-Abschnitt der Binärdatei gespeichert und daher, wenn es Ihnen gelingt, die Adresse eines Shellcodes in __DTOR_END__ zu schreiben, wird dies ausgeführt, bevor das Programm endet.

Holen Sie sich die Adresse dieses Abschnitts mit:

objdump -s -j .dtors /exec
rabin -s /exec | grep “__DTOR”

Normalerweise finden Sie die DTOR-Marker zwischen den Werten ffffffff und 00000000. Wenn Sie also nur diese Werte sehen, bedeutet das, dass keine Funktion registriert ist. Überschreiben Sie also die 00000000 mit der Adresse des Shellcodes, um ihn auszuführen.

Natürlich müssen Sie zuerst einen Speicherplatz für den Shellcode finden, um ihn später aufrufen zu können.

.fini_array

Im Wesentlichen handelt es sich hierbei um eine Struktur mit Funktionen, die aufgerufen werden, bevor das Programm beendet wird, ähnlich wie bei .dtors. Dies ist interessant, wenn Sie Ihren Shellcode aufrufen können, indem Sie zu einer Adresse springen, oder in Fällen, in denen Sie zurück zu main gehen müssen, um die Schwachstelle ein zweites Mal auszunutzen.

objdump -s -j .fini_array ./greeting

./greeting:     file format elf32-i386

Contents of section .fini_array:
8049934 a0850408

#Put your address in 0x8049934

Hinweis: Wenn eine Funktion aus dem .fini_array ausgeführt wird, wird sie zur nächsten verschoben, sodass sie nicht mehrmals ausgeführt wird (um endlose Schleifen zu verhindern), aber es wird Ihnen auch nur 1 Ausführung der Funktion geben, die hier platziert ist.

Beachten Sie, dass Einträge im .fini_array in umgekehrter Reihenfolge aufgerufen werden, daher möchten Sie wahrscheinlich damit beginnen, vom letzten Eintrag aus zu schreiben.

Endlosschleife

Um den .fini_array zu missbrauchen und eine Endlosschleife zu erhalten, können Sie überprüfen, was hier getan wurde: Wenn Sie mindestens 2 Einträge im .fini_array haben, können Sie:

  • Verwenden Sie Ihren ersten Schreibzugriff, um die anfällige willkürliche Schreibfunktion erneut aufzurufen

  • Berechnen Sie dann die Rücksprungadresse im Stack, der von __libc_csu_fini gespeichert ist (die Funktion, die alle .fini_array-Funktionen aufruft) und setzen Sie dort die Adresse von __libc_csu_fini

  • Dadurch wird __libc_csu_fini sich selbst erneut aufrufen und die .fini_array-Funktionen erneut ausführen, die die anfällige WWW-Funktion 2-mal aufrufen werden: einmal für die willkürliche Schreibweise und ein weiteres Mal, um erneut die Rücksprungadresse von __libc_csu_fini im Stack zu überschreiben, um sich selbst erneut aufzurufen.

Beachten Sie, dass bei Full RELRO, der Abschnitt .fini_array schreibgeschützt ist.

Wie in diesem Beitrag erklärt, Wenn das Programm mit return oder exit() beendet wird, wird __run_exit_handlers() ausgeführt, das registrierte Destruktoren aufrufen wird.

Wenn das Programm über die _exit()-Funktion beendet wird, wird der exit-Syscall aufgerufen und die Exit-Handler werden nicht ausgeführt. Um zu bestätigen, dass __run_exit_handlers() ausgeführt wird, können Sie einen Breakpoint darauf setzen.

Der wichtige Code ist (Quelle):

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
}

Beachten Sie, wie map -> l_addr + fini_array -> d_un.d_ptr verwendet wird, um die Position des Arrays von aufzurufenden Funktionen zu berechnen.

Es gibt ein paar Optionen:

  • Überschreiben Sie den Wert von map->l_addr, um ihn auf ein gefälschtes fini_array mit Anweisungen zum Ausführen von beliebigem Code zu verweisen.

  • Überschreiben Sie die Einträge l_info[DT_FINI_ARRAY] und l_info[DT_FINI_ARRAYSZ] (die im Speicher mehr oder weniger aufeinander folgen), um sie auf eine gefälschte Elf64_Dyn-Struktur zu verweisen, die erneut dazu führt, dass array auf einen vom Angreifer kontrollierten Speicherbereich zeigt.

  • Dieser Bericht überschreibt l_info[DT_FINI_ARRAY] mit der Adresse eines vom Angreifer kontrollierten Speichers im .bss, der ein gefälschtes fini_array enthält. Dieses gefälschte Array enthält zuerst eine One-Gadget-Adresse, die ausgeführt wird, und dann den Unterschied zwischen der Adresse dieses gefälschten Arrays und dem Wert von map->l_addr, sodass *array auf das gefälschte Array zeigt.

  • Laut Hauptbeitrag dieser Technik und diesem Bericht hinterlässt ld.so einen Zeiger auf dem Stapel, der auf den binären link_map in ld.so zeigt. Mit einem beliebigen Schreibvorgang ist es möglich, ihn zu überschreiben und auf ein vom Angreifer kontrolliertes gefälschtes fini_array zu verweisen, das die Adresse zu einem One-Gadget enthält, zum Beispiel.

Nach dem vorherigen Code finden Sie einen weiteren interessanten Abschnitt mit dem Code:

/* 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 diesem Fall wäre es möglich, den Wert von map->l_info[DT_FINI] zu überschreiben, der auf eine gefälschte ElfW(Dyn)-Struktur zeigt. Finde hier weitere Informationen.

Überschreiben der TLS-Speicher dtor_list in __run_exit_handlers

Wie hier erklärt, wenn ein Programm über return oder exit() beendet wird, wird es __run_exit_handlers() ausführen, das alle registrierten Destruktoren aufrufen wird.

Code aus _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 ();

Code von __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);
[...]
}
}

Für jede registrierte Funktion in tls_dtor_list wird der Zeiger aus cur->func demangled und mit dem Argument cur->obj aufgerufen.

Mit der tls Funktion aus diesem Fork von GEF ist es möglich zu sehen, dass tatsächlich die dtor_list sehr nah am Stack-Canary und am PTR_MANGLE-Cookie liegt. Daher wäre es bei einem Überlauf möglich, das Cookie und den Stack-Canary zu überschreiben. Durch Überschreiben des PTR_MANGLE-Cookies wäre es möglich, die PTR_DEMANLE-Funktion zu umgehen, indem es auf 0x00 gesetzt wird, was bedeutet, dass das xor, das verwendet wird, um die tatsächliche Adresse zu erhalten, einfach die konfigurierte Adresse ist. Anschließend ist es möglich, durch Schreiben in die dtor_list mehrere Funktionen mit der Funktions-Adresse und ihrem Argument zu verketten.

Schließlich ist zu beachten, dass der gespeicherte Zeiger nicht nur mit dem Cookie xor-verknüpft wird, sondern auch um 17 Bits rotiert wird:

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

So müssen Sie dies berücksichtigen, bevor Sie eine neue Adresse hinzufügen.

Finden Sie ein Beispiel im Originalbeitrag.

Andere veränderte Zeiger in __run_exit_handlers

Diese Technik wird hier erklärt und hängt erneut davon ab, dass das Programm beendet wird, indem return oder exit() aufgerufen wird, sodass __run_exit_handlers() aufgerufen wird.

Lassen Sie uns mehr Code dieser Funktion überprüfen:

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

Die Variable f zeigt auf die Struktur initial und je nach Wert von f->flavor werden unterschiedliche Funktionen aufgerufen. Je nach Wert befindet sich die Adresse der aufzurufenden Funktion an einem anderen Ort, aber sie wird immer demangled sein.

Darüber hinaus ist es in den Optionen ef_on und ef_cxa auch möglich, ein Argument zu steuern.

Es ist möglich, die initial Struktur in einer Debugging-Sitzung mit GEF auszuchecken, indem man gef> p initial ausführt.

Um dies auszunutzen, müssen Sie entweder das PTR_MANGLE-Cookie leaken oder löschen und dann einen cxa-Eintrag in initial mit system('/bin/sh') überschreiben. Ein Beispiel hierfür finden Sie im ursprünglichen Blog-Beitrag über die Technik.

Erlernen Sie AWS-Hacking von Grund auf mit htARTE (HackTricks AWS Red Team Expert)!

Andere Möglichkeiten, HackTricks zu unterstützen:

Last updated