WWW2Exec - atexit(), TLS-Speicher & andere beschädigte Zeiger

Reading time: 9 minutes

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Unterstützen Sie HackTricks

__atexit-Strukturen

caution

Heutzutage ist es sehr seltsam, dies auszunutzen!

atexit() ist eine Funktion, der andere Funktionen als Parameter übergeben werden. Diese Funktionen werden ausgeführt, wenn ein exit() oder die Rückkehr von der main erfolgt.
Wenn Sie die Adresse einer dieser Funktionen so modifizieren können, dass sie beispielsweise auf einen Shellcode zeigt, werden Sie Kontrolle über den Prozess erlangen, aber das ist derzeit komplizierter.
Derzeit sind die Adressen der auszuführenden Funktionen hinter mehreren Strukturen versteckt und schließlich zeigen die Adressen, auf die sie verweisen, nicht auf die Adressen der Funktionen, sondern sind mit XOR und Verschiebungen mit einem zufälligen Schlüssel verschlüsselt. Daher ist dieser Angriffsvektor derzeit nicht sehr nützlich, zumindest nicht auf x86 und x64_86.
Die Verschlüsselungsfunktion ist PTR_MANGLE. Andere Architekturen wie m68k, mips32, mips64, aarch64, arm, hppa... implementieren die Verschlüsselung nicht, da sie das gleiche zurückgeben, was sie als Eingabe erhalten haben. Diese Architekturen wären also durch diesen Vektor angreifbar.

Eine ausführliche Erklärung, wie das funktioniert, finden Sie unter https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html

Wie in diesem Beitrag erklärt, wenn das Programm mit return oder exit() beendet wird, wird __run_exit_handlers() aufgerufen, das registrierte Destruktoren aufruft.

caution

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 Haltepunkt darauf setzen.

Der wichtige Code ist (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
}

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

Es gibt eine Handvoll 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 mehr oder weniger aufeinanderfolgend im Speicher sind), um sie auf eine gefälschte Elf64_Dyn-Struktur zeigen zu lassen, die erneut array auf eine Speicherzone zeigt, die der Angreifer kontrolliert.
  • Dieser Bericht überschreibt l_info[DT_FINI_ARRAY] mit der Adresse eines kontrollierten Speichers in .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 die Differenz zwischen der Adresse dieses gefälschten Arrays und dem Wert von map->l_addr, sodass *array auf das gefälschte Array zeigt.
  • Laut dem Hauptbeitrag dieser Technik und diesem Bericht hinterlässt ld.so einen Zeiger auf dem Stack, der auf die binäre link_map in ld.so zeigt. Mit einem beliebigen Schreibvorgang ist es möglich, ihn zu überschreiben und ihn auf ein gefälschtes fini_array zu verweisen, das vom Angreifer mit der Adresse zu einem one gadget kontrolliert wird, zum Beispiel.

Im Anschluss an den vorherigen Code finden Sie einen weiteren interessanten Abschnitt mit dem Code:

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

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. Finden Sie weitere Informationen hier.

TLS-Storage dtor_list Überschreibung in __run_exit_handlers

Wie hier erklärt wird, wenn ein Programm über return oder exit() beendet wird, __run_exit_handlers() ausgeführt, das alle registrierten Destruktorsfunktionen aufruft.

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

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

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

Mit der tls-Funktion aus diesem Fork von GEF ist es möglich zu sehen, dass die dtor_list tatsächlich sehr nahe am Stack Canary und dem PTR_MANGLE-Cookie ist. Ein Überlauf darauf würde es ermöglichen, das Cookie und den Stack Canary zu überschreiben.
Durch das Überschreiben des PTR_MANGLE-Cookies wäre es möglich, die PTR_DEMANLE-Funktion zu umgehen, indem man es auf 0x00 setzt, was bedeutet, dass das xor, das verwendet wird, um die echte Adresse zu erhalten, nur die konfigurierte Adresse ist. Dann ist es durch das Schreiben auf die dtor_list möglich, mehrere Funktionen mit der Funktionsadresse und ihrem Argument zu verknüpfen.

Beachten Sie schließlich, dass der gespeicherte Zeiger nicht nur mit dem Cookie xored, sondern auch um 17 Bits rotiert wird:

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

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

Finden Sie ein Beispiel im originalen Beitrag.

Andere beschädigte Zeiger in __run_exit_handlers

Diese Technik ist 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:

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

Die Variable f verweist auf die initial Struktur und je nach Wert von f->flavor werden unterschiedliche Funktionen aufgerufen.
Je nach Wert wird die Adresse der aufzurufenden Funktion an einem anderen Ort sein, 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 zu überprüfen, indem man gef> p initial eingibt.

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

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Unterstützen Sie HackTricks