WWW2Exec - .dtors & .fini_array
.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:
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.
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.
link_map
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):
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älschtesfini_array
mit Anweisungen zum Ausführen von beliebigem Code zu verweisen.Überschreiben Sie die Einträge
l_info[DT_FINI_ARRAY]
undl_info[DT_FINI_ARRAYSZ]
(die im Speicher mehr oder weniger aufeinander folgen), um sie auf eine gefälschteElf64_Dyn
-Struktur zu verweisen, die erneut dazu führt, dassarray
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älschtesfini_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 vonmap->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älschtesfini_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:
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
__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()
:
Code von __call_tls_dtors()
:
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:
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
__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:
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.
Last updated