<summary><strong>Erfahren Sie AWS-Hacking von Null auf Held mit</strong><ahref="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
* Wenn Sie Ihr **Unternehmen in HackTricks beworben sehen möchten** oder **HackTricks im PDF-Format herunterladen möchten**, überprüfen Sie die [**ABONNEMENTPLÄNE**](https://github.com/sponsors/carlospolop)!
* Holen Sie sich das [**offizielle PEASS & HackTricks-Merch**](https://peass.creator-spring.com)
* **Treten Sie der** 💬 [**Discord-Gruppe**](https://discord.gg/hRep4RUj7f) oder der [**Telegram-Gruppe**](https://t.me/peass) bei oder **folgen** Sie uns auf **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
* **Teilen Sie Ihre Hacking-Tricks, indem Sie PRs an die** [**HackTricks**](https://github.com/carlospolop/hacktricks) und [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) GitHub-Repositories senden.
Para verificar, ob die Systemaufrufe korrekt ausgeführt werden, muss das vorherige Programm kompiliert werden und die Systemaufrufe sollten in **strace ./KOMPILIERTE\_PROGRAMM** erscheinen.
Beim Erstellen von Shellcodes kann ein Trick angewendet werden. Die erste Anweisung ist ein Sprung zu einem Aufruf. Der Aufruf ruft den Originalcode auf und legt zusätzlich das EIP im Stack ab. Nach der Aufrufanweisung haben wir den benötigten String platziert, sodass wir mit diesem EIP auf den String zeigen und den Code weiter ausführen können.
Besteht aus einem kleinen Code, der die Speicherseiten eines Prozesses nach der dort gespeicherten Shellcode durchsucht (sucht nach einer Signatur im Shellcode). Nützlich in Fällen, in denen nur wenig Platz zum Einspritzen von Code zur Verfügung steht.
Es handelt sich um verschlüsselte Shells, die über kleine Codes verfügen, die sie entschlüsseln und zu ihnen springen lassen, unter Verwendung des Call-Pop-Tricks wäre dies ein **Beispiel für eine Caesar-Verschlüsselung**:
De esta forma, wenn das EBP beim Verlassen einer Funktion (fvuln), die von einer anderen Funktion aufgerufen wurde, geändert werden kann, kann das EIP geändert werden, wenn die Funktion, die fvuln aufgerufen hat, endet.
In fvuln kann ein falsches EBP eingefügt werden, das auf eine Stelle zeigt, an der die Adresse des Shellcodes + 4 (4 für das Pop) liegt. So wird beim Verlassen der Funktion der Wert von &(\&Shellcode)+4 in ESP platziert, mit dem Pop wird 4 von ESP abgezogen und er zeigt auf die Adresse des Shellcodes, wenn das ret ausgeführt wird.
Es ist nur möglich, das am wenigsten signifikante Byte des EBP zu ändern. Ein Angriff wie oben beschrieben kann durchgeführt werden, aber der Speicher, der die Adresse des Shellcodes speichert, muss die ersten 3 Bytes mit dem EBP teilen.
ASLR bewirkt, dass Funktionen bei jeder Ausführung an unterschiedlichen Speicherpositionen geladen werden. Daher kann diese Methode in diesem Fall möglicherweise nicht wirksam sein. Für Remote-Server, da das Programm ständig an derselben Adresse ausgeführt wird, kann es jedoch nützlich sein.
Die Adresse der libc-Systemanweisung wird platziert und der String "/bin/sh" wird als Argument übergeben, normalerweise aus einer Umgebungsvariable. Außerdem wird die Adresse der exit-Funktion verwendet, damit das Programm nach Beendigung der Shell ohne Probleme beendet wird (und Logs geschrieben werden).
“A” \* EBP-ABSTAND + 4 (EBP: können 4 "A"s sein, aber es ist besser, wenn es das echte EBP ist, um Segmentierungsfehler zu vermeiden) + Adresse von **system** (überschreibt das EIP) + Adresse von **exit** (beim Verlassen von system(“/bin/sh”) wird diese Funktion aufgerufen, da die ersten 4 Bytes des Stacks als die nächste auszuführende EIP-Adresse behandelt werden) + Adresse von “**/bin/sh**” (wird als Parameter an system übergeben)
Auf diese Weise wird das EIP mit der Adresse von system überschrieben, die den String "/bin/sh" als Parameter erhält und beim Verlassen dieses die Funktion exit() ausführt.
Es kann vorkommen, dass ein Byte einer Adressfunktion null oder Leerzeichen (\x20) ist. In diesem Fall können die vorherigen Adressen disassembliert werden, da wahrscheinlich mehrere NOPs vorhanden sind, die es ermöglichen, einen von ihnen anstelle der Funktion direkt aufzurufen (zum Beispiel mit > x/8i system-4).
Diese Methode funktioniert, da beim Aufruf einer Funktion wie system mit dem Opcode **ret** anstelle von **call** die Funktion davon ausgeht, dass die ersten 4 Bytes die **EIP**-Adresse sind, zu der zurückgekehrt werden soll.
Eine interessante Technik bei dieser Methode ist das Aufrufen von **strncpy()**, um ein Nutzlast vom Stack auf den Heap zu verschieben und anschließend **gets()** zu verwenden, um diese Nutzlast auszuführen.
Eine weitere interessante Technik ist die Verwendung von **mprotect()**, die es ermöglicht, die gewünschten Berechtigungen für jeden Teil des Speichers zuzuweisen. Es funktionierte oder funktionierte in BDS, MacOS und OpenBSD, aber nicht in Linux (es wird kontrolliert, dass Schreib- und Ausführungsberechtigungen nicht gleichzeitig erteilt werden können). Mit diesem Angriff könnte der Stack wieder als ausführbar konfiguriert werden.
Auf diese Weise können Funktionen verkettet werden, die aufgerufen werden sollen. Außerdem können, wenn Funktionen mit mehreren Argumenten verwendet werden sollen, die erforderlichen Argumente (z. B. 4) platziert und die 4 Argumente eingegeben werden und nach einer Adresse mit Opcodes gesucht werden: pop, pop, pop, pop, ret —> **objdump -d ausführbare Datei**
Dieser Shellcode kann endlos in den Speicherbereichen wiederholt werden, auf die zugegriffen werden kann, sodass ein Shellcode leicht in kleine Speicherstücke unterteilt werden kann.
Nützlich, wenn eine Adresse vom Stack nicht in das EIP eingefügt werden kann (es wird überprüft, dass das EIP nicht 0xbf enthält) oder wenn der Speicherort des Shellcodes nicht berechnet werden kann. Die anfällige Funktion akzeptiert jedoch einen Parameter (der Shellcode wird hier platziert).
Auf diese Weise wird durch Ändern des EIP in eine Adresse zu einem **ret** die nächste Adresse geladen (die die Adresse des ersten Arguments der Funktion ist). Das bedeutet, dass der Shellcode geladen wird.
Der Exploit würde sein: SHELLCODE + Füllung (bis EIP) + **\&ret** (die nächsten Bytes des Stacks zeigen auf den Beginn des Shellcodes, da die Adresse des übergebenen Parameters in den Stack eingefügt wird)
Es scheint, dass Funktionen wie **strncpy** nach Abschluss die Adresse, an der der Shellcode gespeichert war, vom Stack entfernen und diese Technik unmöglich machen. Das bedeutet, dass die Adresse, die der Funktion als Argument übergeben wird (die den Shellcode speichert), durch ein 0x00 ersetzt wird, sodass beim zweiten **ret** ein 0x00 gefunden wird und das Programm abstürzt.
Si wir keine Kontrolle über das erste Argument haben, aber über das zweite oder dritte, können wir EIP mit einer Adresse zu pop-ret oder pop-pop-ret überschreiben, je nach Bedarf.
Durch das Betrachten des Aufbaus des Stacks eines neuen Prozesses in Linux kann ein Exploit entwickelt werden, sodass das Programm in einer Umgebung gestartet wird, in der nur eine Variable vorhanden ist: die Shellcode. Die Adresse dieser kann dann berechnet werden als: addr = 0xbfffffff - 4 - strlen(KOMPLETTER_AUSFÜHRBARER_NAME) - strlen(shellcode)
Da ESP immer auf den Beginn des Stacks zeigt, besteht diese Technik darin, EIP durch die Adresse eines **jmp esp** oder **call esp** zu ersetzen. Auf diese Weise wird der Shellcode nach der Überschreibung des EIP gespeichert, da nach dem Ausführen des **ret** ESP auf die nächste Adresse zeigt, genau dort, wo der Shellcode gespeichert wurde.
Wenn ASLR in Windows oder Linux nicht aktiviert ist, können **jmp esp** oder **call esp** aus einem gemeinsam genutzten Objekt aufgerufen werden. Wenn ASLR aktiviert ist, könnte innerhalb des anfälligen Programms gesucht werden.
Darüber hinaus ermöglicht die Platzierung des Shellcodes nach der EIP-Korruption anstelle in der Mitte des Stacks, dass die push- oder pop-Anweisungen, die während der Funktion ausgeführt werden, den Shellcode nicht berühren (was passieren könnte, wenn er in der Mitte des Stacks der Funktion platziert würde).
Auf ähnliche Weise, wenn bekannt ist, dass eine Funktion die Adresse speichert, an der der Shellcode gespeichert ist, kann **call eax** oder **jmp eax (ret2eax)** aufgerufen werden.
Diese Art von Überläufen tritt auf, wenn eine Variable nicht darauf vorbereitet ist, eine so große Zahl wie die übergebene zu verarbeiten, möglicherweise aufgrund einer Verwechslung zwischen vorzeichenbehafteten und vorzeichenlosen Variablen, zum Beispiel:
En el vorherigen Beispiel sehen wir, dass das Programm 2 Parameter erwartet. Der erste ist die Länge des folgenden Strings und der zweite ist der String.
Wenn wir als ersten Parameter eine negative Zahl übergeben, wird angezeigt, dass len <256istundwirdiesenFilterpassieren,außerdemwirdauchstrlen(buffer)kleineralslsein,daleinunsignedintistundsehrgroßseinwird.
Diese Art von Overflows zielt nicht darauf ab, etwas im Prozess des Programms zu schreiben, sondern darauf, schlecht gestaltete Filter zu umgehen, um andere Schwachstellen auszunutzen.
Es ist nicht bekannt, welchen Wert eine nicht initialisierte Variable annehmen kann, und es könnte interessant sein, dies zu beobachten. Es könnte sein, dass sie den Wert annimmt, den eine Variable aus der vorherigen Funktion hatte und diese vom Angreifer kontrolliert wird.
In C ist **`printf`** eine Funktion, die verwendet werden kann, um einen String auszugeben. Der **erste Parameter**, den diese Funktion erwartet, ist der **Roh-Text mit den Formatierern**. Die **folgenden erwarteten Parameter** sind die **Werte**, die die **Formatierer** im Roh-Text **ersetzen** sollen.
Die Schwachstelle tritt auf, wenn ein **Angreifertext als erster Argument** an diese Funktion übergeben wird. Der Angreifer kann einen **speziellen Input erstellen**, indem er die **Fähigkeiten der printf-Formatzeichenfolge missbraucht**, um **beliebige Daten an beliebige Adressen zu schreiben**. Auf diese Weise kann er **beliebigen Code ausführen**.
**`%n`** **schreibt** die **Anzahl der geschriebenen Bytes** in die **angegebene Adresse. Indem** wir so viele **Bytes** schreiben, wie die Hexadezimalzahl, die wir **schreiben müssen**, können wir **beliebige Daten schreiben**.
Beobachten Sie, wie Sie nach dem **Laden** des **ausführbaren** Codes in GEF die **Funktionen** sehen können, die sich im **GOT** befinden: `gef➤ x/20x 0xDIR_GOT`
In einem Binärfile enthält das GOT die **Adressen der Funktionen oder** des **PLT**-Abschnitts, der die Funktionsadresse lädt. Das Ziel dieses Exploits ist es, den **GOT-Eintrag** einer Funktion zu **überschreiben**, die später ausgeführt wird, **mit** der **Adresse** des PLT der **`system`**-**Funktion**. Idealerweise überschreiben Sie den **GOT** einer **Funktion**, die **mit von Ihnen kontrollierten Parametern** aufgerufen wird (damit Sie die an die Systemfunktion gesendeten Parameter kontrollieren können).
Wenn **`system`** **nicht vom Skript verwendet wird**, wird die Systemfunktion **keinen Eintrag im GOT haben**. In diesem Szenario müssen Sie zuerst die Adresse der `system`-Funktion **leaken**.
**Procedure Linkage Table** ist eine **schreibgeschützte** Tabelle in der ELF-Datei, die alle erforderlichen **Symbole speichert, die aufgelöst werden müssen**. Wenn eine dieser Funktionen aufgerufen wird, wird das **GOT** den **Fluss** zum **PLT umleiten**, damit es die **Adresse** der Funktion auflösen und sie im GOT speichern kann.\
Dann wird beim **nächsten Mal**, wenn an diese Adresse ein Aufruf erfolgt, die **Funktion direkt aufgerufen**, ohne sie erneut auflösen zu müssen.
Wie bereits erklärt, besteht das Ziel darin, die **Adresse** einer **Funktion** in der **GOT-Tabelle zu überschreiben**, die später aufgerufen wird. Idealerweise könnten wir die **Adresse auf einen Shellcode** setzen, der sich in einem ausführbaren Abschnitt befindet, aber es ist sehr wahrscheinlich, dass Sie keinen Shellcode in einem ausführbaren Abschnitt schreiben können.\
Eine andere Option besteht darin, eine **Funktion zu überschreiben**, die ihre **Argumente** vom **Benutzer erhält** und sie auf die **`system`**-**Funktion** verweist.
Um die Adresse zu schreiben, werden normalerweise 2 Schritte ausgeführt: Sie **schreiben zuerst 2 Bytes** der Adresse und dann die anderen 2. Dazu wird **`$hn`** verwendet.
Im Wesentlichen handelt es sich dabei um eine Struktur mit **Funktionen, die aufgerufen werden**, bevor das Programm endet. Dies ist interessant, wenn Sie Ihren **Shellcode aufrufen können, indem Sie zu einer Adresse springen**, oder in Fällen, in denen Sie erneut zum Hauptprogramm zurückkehren müssen, um **den Formatstring ein zweites Mal auszunutzen**.
Hinweis, dass dies **keine****endlose Schleife** erzeugt, da der Canary bemerken wird, dass das Ende des Stapels möglicherweise beschädigt ist und die Funktion nicht erneut aufgerufen wird. Daher können Sie mit diesem **1 weitere Ausführung** der Schwachstelle haben.
Ein Format-String kann auch missbraucht werden, um Inhalte aus dem Speicher des Programms zu **dumpen**.\
Zum Beispiel gibt es in der folgenden Situation eine **lokale Variable im Stapel, die auf eine Flagge zeigt**. Wenn Sie herausfinden, wo im **Speicher** der **Zeiger** auf die **Flagge** ist, können Sie **printf** dazu bringen, auf diese **Adresse** zuzugreifen und die **Flagge** auszugeben:
Beachten Sie, dass Sie nach dem **vorherigen Exploit** und der Erkenntnis, dass Sie Inhalte **leaken** können, **Zeiger** auf **`printf`** auf den Abschnitt setzen können, in dem das **ausführbare** Programm **geladen** ist, und es **vollständig dumpen**!
Die Destruktoren sind Funktionen, die **ausgeführt werden, bevor das Programm endet**.\
Wenn es Ihnen gelingt, eine **Adresse** zu einem **Shellcode** in **`__DTOR_END__`** zu **schreiben**, wird dies **ausgeführt**, bevor das Programm endet.\
Normalerweise finden Sie den **DTOR**-Abschnitt **zwischen** den Werten `ffffffff` und `00000000`. Wenn Sie also nur diese Werte sehen, bedeutet dies, dass **keine Funktion registriert ist**. Überschreiben Sie also die **`00000000`** mit der **Adresse** des **Shellcodes**, um ihn auszuführen.
**sprintf** verschiebt einen formatierten String **in** eine **Variable**. Daher könnten Sie die **Formatierung** eines Strings missbrauchen, um einen **Pufferüberlauf in der Variablen** zu verursachen, in die der Inhalt kopiert wird.\
**`atexit()`** ist eine Funktion, der **andere Funktionen als Parameter übergeben werden**. Diese **Funktionen** werden ausgeführt, wenn ein **`exit()`** ausgeführt wird oder das **Hauptprogramm** beendet wird.\
Wenn Sie die **Adresse** einer dieser **Funktionen** so ändern können, dass sie beispielsweise auf einen Shellcode zeigt, können Sie die **Kontrolle** über den **Prozess** erlangen, aber dies ist derzeit komplizierter.\
Derzeit sind die **Adressen der auszuführenden Funktionen** hinter mehreren Strukturen versteckt, und schließlich sind die Adressen, auf die sie zeigen, nicht die Adressen der Funktionen, sondern sind **verschlüsselt mit XOR** und Verschiebungen mit einem **zufälligen Schlüssel**. Daher ist dieser Angriffsvektor derzeit **zumindest auf x86** und **x64\_86** nicht sehr nützlich.\
Die **Verschlüsselungsfunktion** ist **`PTR_MANGLE`**. **Andere Architekturen** wie m68k, mips32, mips64, aarch64, arm, hppa... **implementieren die Verschlüsselungsfunktion nicht**, da sie das Gleiche zurückgeben wie sie als Eingabe erhalten haben. Daher wären diese Architekturen durch diesen Vektor angreifbar.
**`Setjmp()`** ermöglicht es, den **Kontext** (die Register) zu **speichern**.\
**`longjmp()`** ermöglicht es, den **Kontext** zu **wiederherstellen**.\
Die **gespeicherten Register** sind: `EBX, ESI, EDI, ESP, EIP, EBP`\
Das Problem ist, dass EIP und ESP durch die **`PTR_MANGLE`**-Funktion übergeben werden, sodass die **Architektur, die anfällig für diesen Angriff ist, die gleiche ist wie oben**.\
Sie sind nützlich für Fehlerbehebung oder Unterbrechungen.\
Jedoch sind nach meinen Recherchen die anderen Register nicht geschützt, **so dass bei einem `call ebx`, `call esi` oder `call edi`** innerhalb der aufgerufenen Funktion die Kontrolle übernommen werden kann. Oder Sie könnten auch EBP ändern, um ESP zu ändern.
Jedes Objekt einer **Klasse** hat einen **VPtr**, der ein **Zeiger** auf das Array seiner Klasse ist. Der VPtr ist Teil des Headers jedes Objekts, sodass bei einer **Überschreibung** des **VPtr** könnte er **geändert** werden, um auf eine Dummy-Methode zu zeigen, sodass bei der Ausführung einer Funktion der Shellcode aufgerufen wird.
Es handelt sich um eine Technik, um einen Pufferüberlauf in einen Formatkettenfehler umzuwandeln. Dabei wird der EIP so ersetzt, dass er auf ein printf der Funktion zeigt und eine manipulierte Formatkette als Argument übergeben wird, um Werte über den Zustand des Prozesses zu erhalten.
Bibliotheken befinden sich an einer Position mit 16 Bits Zufälligkeit = 65636 mögliche Adressen. Wenn ein anfälliger Server fork() aufruft, wird der Speicheradressraum im Kindprozess dupliziert und bleibt intakt. Daher kann versucht werden, einen Brute-Force-Angriff auf die usleep() Funktion von libc durchzuführen, indem "16" als Argument übergeben wird, sodass, wenn die Antwort länger als üblich dauert, diese Funktion gefunden wurde. Wenn bekannt ist, wo sich diese Funktion befindet, kann delta\_mmap erhalten und die anderen berechnet werden.
Die einzige Möglichkeit, sicherzustellen, dass ASLR funktioniert, besteht darin, eine 64-Bit-Architektur zu verwenden. Dort gibt es keine Brute-Force-Angriffe.
**Relro (Read only Relocation)** beeinflusst die Speicherberechtigungen ähnlich wie NX. Der Unterschied besteht darin, dass während NX den Stack ausführbar macht, RELRO bestimmte Dinge schreibgeschützt macht, sodass wir nicht darauf schreiben können. Der häufigste Weg, wie ich gesehen habe, dass dies ein Hindernis darstellt, besteht darin, uns daran zu hindern, eine **`got`-Tabelle zu überschreiben**, was später behandelt wird. Die `got`-Tabelle enthält Adressen für libc-Funktionen, damit das Binärprogramm weiß, welche Adressen es sind und sie aufrufen kann. Schauen wir uns an, wie die Speicherberechtigungen für einen `got`-Tabelleneintrag für ein Binärprogramm mit und ohne Relro aussehen.
Für die Binärdatei **ohne relro** können wir sehen, dass die `got`-Eintragsadresse für `fgets``0x404018` ist. Wenn wir uns die Speicherzuordnungen ansehen, sehen wir, dass sie zwischen `0x404000` und `0x405000` liegt, was die **Berechtigungen `rw`** hat, was bedeutet, dass wir darauf lesen und schreiben können. Für die Binärdatei **mit relro** sehen wir, dass die Adresse der `got`-Tabelle für die Ausführung der Binärdatei (pie ist aktiviert, daher wird sich diese Adresse ändern) `0x555555557fd0` ist. In der Speicherzuordnung dieser Binärdatei liegt sie zwischen `0x0000555555557000` und `0x0000555555558000`, was die Speicherberechtigung `r` bedeutet, was bedeutet, dass wir nur daraus lesen können.
Also, was ist die **Umgehung**? Die typische Umgehung, die ich verwende, ist einfach nicht in Speicherbereiche zu schreiben, die durch relro schreibgeschützt werden, und **einen anderen Weg finden, um Codeausführung zu erreichen**.
* Lazy Binding: Die Adresse einer Funktion wird beim ersten Aufruf der Funktion gesucht. Daher muss die GOT während der Ausführung Schreibberechtigungen haben.
* Bind now: Die Adressen der Funktionen werden zu Beginn der Ausführung gelöst, dann werden schreibgeschützte Berechtigungen für sensible Abschnitte wie .got, .dtors, .ctors, .dynamic, .jcr vergeben. `` `** ``-z relro`**`y`**`-z now\`\*\*
Cuando der Binärdatei im Speicher geladen wird und eine Funktion zum ersten Mal aufgerufen wird, wird zur PLT (Procedure Linkage Table) gesprungen, von dort aus wird zu der GOT gesprungen und festgestellt, dass dieser Eintrag nicht aufgelöst wurde (enthält eine Adresse nach der PLT). Daher ruft es den Laufzeit-Linker oder rtfd auf, um die Adresse aufzulösen und in der GOT zu speichern.
Wenn eine Funktion aufgerufen wird, wird die PLT aufgerufen, die die Adresse der GOT enthält, in der die Adresse der Funktion gespeichert ist, und leitet den Fluss dorthin um, um die Funktion aufzurufen. Wenn jedoch die Funktion zum ersten Mal aufgerufen wird, enthält die GOT die nächste Anweisung der PLT, daher folgt der Fluss dem PLT-Code (rtfd) und ermittelt die Adresse der Funktion, speichert sie in der GOT und ruft sie auf.
Beim Laden einer Binärdatei in den Speicher hat der Compiler angegeben, an welcher Offset Daten platziert werden müssen, die geladen werden sollen, wenn das Programm ausgeführt wird.
Lazy binding -> Die Adresse der Funktion wird gesucht, wenn die Funktion zum ersten Mal aufgerufen wird, daher hat die GOT Schreibberechtigungen, damit sie dort gespeichert wird und nicht erneut gesucht werden muss.
Bind now -> Die Adressen der Funktionen werden beim Laden des Programms gesucht und die Berechtigungen der Abschnitte .got, .dtors, .ctors, .dynamic, .jcr werden auf nur Lesen geändert. **-z relro** und **-z now**
Versucht, unsichere Funktionen zu identifizieren, die Daten unsicher von einem Ort zum anderen kopieren, und ersetzt die Funktion durch eine sichere Funktion.
Es wird als unsicher identifiziert und dann wird strcpy() durch \_\_strcpy\_chk() ersetzt, wobei die Puffergröße als maximale zu kopierende Größe verwendet wird.
Die zweite Option verhindert, dass **%n** aus einem Abschnitt mit Schreibberechtigung stammt. Außerdem kann der Parameter für den direkten Zugriff auf Argumente nur verwendet werden, wenn die vorherigen verwendet wurden, d.h. **%3$d** kann nur verwendet werden, wenn zuvor **%2$d** und **%1$d** verwendet wurden.
Um die Fehlermeldung anzuzeigen, wird argv\[0] verwendet, daher wird, wenn dort die Adresse eines anderen Ortes (wie einer globalen Variablen) angegeben wird, der Inhalt dieser Variablen in der Fehlermeldung angezeigt. Seite 191
Es werden unsichere Funktionsaufrufe durch sichere ersetzt. Es ist nicht standardisiert. (nur für x86, nicht für Kompilierungen mit -fomit-frame-pointer, nicht für statische Kompilierungen, nicht alle anfälligen Funktionen werden sicher gemacht und LD_PRELOAD funktioniert nicht bei SUID-Binärdateien).
Es beinhaltet das Laden von gemeinsam genutzten Bibliotheken von 0x00000000 bis 0x00ffffff, um sicherzustellen, dass immer ein Byte 0x00 vorhanden ist. Dies hält jedoch kaum einen Angriff auf, insbesondere nicht in Little Endian.
Es beinhaltet die Durchführung eines ROP, bei dem die Funktion strcpy@plt (aus der plt) aufgerufen wird und auf den Eintrag der GOT gezeigt wird, und das erste Byte der Funktion, die aufgerufen werden soll (system()), kopiert wird. Anschließend wird dasselbe mit GOT+1 gemacht und das 2. Byte von system() kopiert... Schließlich wird die in der GOT gespeicherte Adresse aufgerufen, die system() sein wird.
Für Funktionen, die EBP als Register verwenden, um auf Argumente zu zeigen, wenn der EIP geändert wird und auf system() gezeigt wird, muss auch EBP geändert werden, um auf einen Speicherbereich zu zeigen, der 2 beliebige Bytes und dann die Adresse zu &”/bin/sh” enthält.
Die freien Stücke sind in einer doppelt verketteten Liste (bin) und es dürfen niemals zwei freie Stücke nebeneinander sein (sie werden zusammengeführt).
Im "size"-Feld gibt es Bits, um anzuzeigen: ob das vorherige Stück in Benutzung ist, ob das Stück über mmap() zugewiesen wurde und ob das Stück zum primären Arena gehört.
Wenn ein Stück freigegeben wird und eines der benachbarten Stücke frei ist, werden sie durch die Makro unlink() fusioniert und das größere neue Stück wird frontlink() übergeben, um es in den entsprechenden Bin einzufügen.
Daher, wenn es uns gelingt, P->bk mit der Adresse eines Shellcodes und P->fd mit der Adresse eines Eintrags in der GOT oder DTORS minus 12 zu ändern, wird erreicht:
BK->fd = FD -> \*(\&shellcode + 8) = (&\_\_dtor\_end\_\_ - 12) —> Dies führt dazu, dass 4 Bytes ab dem 8. Byte des Shellcodes geschrieben werden, daher muss die erste Anweisung des Shellcodes ein Sprung sein, um dies zu überspringen und zu den Nops zu gelangen, die den Rest des Shellcodes ausführen.
Im Puffer1 wird der Shellcode platziert, beginnend mit einem Sprung, damit er zu den Nops oder dem Rest des Shellcodes gelangt.
Nach dem Shellcode wird Füllmaterial eingefügt, bis zum prev_size- und size-Feld des nächsten Stücks. An diesen Stellen werden 0xfffffff0 (um das prev_size zu überschreiben, damit das Bit angezeigt wird, dass es frei ist) und "-4" (0xfffffffc) in die size eingefügt (damit, wenn im 3. Stück überprüft wird, ob das 2. frei war, tatsächlich auf das modifizierte prev_size zugegriffen wird, das angibt, dass es frei ist) -> Wenn also free() überprüft, wird es zur size des 3. Stücks gehen, aber tatsächlich zum 2. - 4 und denken, dass das 2. Stück frei ist. Dann wird unlink() aufgerufen.
Beim Aufruf von unlink() werden die ersten Daten des 2. Stücks als P->fd verwendet, daher wird dort die Adresse eingefügt, die überschrieben werden soll - 12 (da FD->bk 12 zur in FD gespeicherten Adresse hinzufügt). An dieser Adresse wird die zweite Adresse im 2. Stück eingefügt, die die Adresse des Shellcodes sein soll (falsches P->bk).
**fake\_size = pack("\<I”, 0xfffffffc) #-4, damit der "size" des 3. Chunks 4 Bytes hinter dem vorherigen liegt (zeigt auf prev\_size), da dort überprüft wird, ob der 2. Chunk frei ist**
**got\_free = pack("\<I", 0x08048300 - 12) #Adresse von free() in der plt-12 (wird überschrieben, um die Shellcode beim zweiten Aufruf von free() auszuführen)**
**payload += prev\_size + fake\_size + got\_free + addr\_sc #Der 2. Chunk wird modifiziert, got\_free zeigt auf die Stelle, an der die Adresse addr\_sc + 12 gespeichert wird**
Daher wird das Programm denken, dass "a" frei ist und in einem Bin liegt, und unlink() aufrufen, um es zu entkoppeln. Da jedoch der Header PREV\_SIZE -4 beträgt, wird angenommen, dass der "a"-Chunk tatsächlich bei b+4 beginnt. Mit anderen Worten, es wird unlink() auf einen Chunk ausgeführt, der bei b+4 beginnt, sodass sich der Pointer "fd" bei b+12 und der Pointer "bk" bei b+16 befinden.
Frontlink wird aufgerufen, wenn etwas freigegeben wird und keiner seiner benachbarten Chunks frei ist. unlink() wird nicht aufgerufen, sondern frontlink() wird direkt aufgerufen.
Durch das Überlaufen von zwei mallocs auf unkontrollierte Weise und eines auf kontrollierte Weise, der nur freigegeben wird, kann ein Exploit durchgeführt werden.
Wenn einer erneut verwendet werden soll, wird er problemlos zugewiesen. Wenn der andere verwendet werden soll, wird ihm derselbe Speicherplatz zugewiesen, sodass die Pointer "fd" und "bk" mit den Daten gefälscht werden, die die vorherige Reservierung schreibt.
Nur ein Aufruf von free() ist erforderlich, um die Ausführung beliebigen Codes zu verursachen. Es ist wichtig, einen zweiten Chunk zu finden, der von einem vorherigen überlaufen und freigegeben werden kann.
In \[1] wird das Feld size und das Bit NON\_MAIN\_ARENA überprüft, das geändert werden kann, damit die Überprüfung true zurückgibt und heap\_for\_ptr() ausgeführt wird, das ein "and" auf "mem" ausführt und die 2,5 unwichtigsten Bytes auf 0 setzt (in unserem Fall von 0x0804a000 auf 0x08000000) und auf 0x08000000->ar\_ptr zugreift (als ob es ein struct heap\_info wäre).
Auf diese Weise, wenn wir beispielsweise einen Chunk bei 0x0804a000 kontrollieren und ein Chunk bei **0x081002a0** freigegeben wird, können wir die Adresse 0x08100000 erreichen und beliebige Daten schreiben, z.B. **0x0804a000**. Wenn dieser zweite Chunk freigegeben wird, wird heap\_for\_ptr(ptr)->ar\_ptr den Wert zurückgeben, den wir in 0x08100000 geschrieben haben (da auf 0x081002a0 das zuvor erwähnte "and" angewendet wird und von dort der Wert der ersten 4 Bytes, ar\_ptr, abgeleitet wird).
Daher, wenn wir den Wert von \_\_DTOR\_END\_\_-12 in av->bins\[2] schreiben, wird in der letzten Anweisung in \_\_DTOR\_END\_\_ die Adresse des zweiten Chunks geschrieben.
Mit anderen Worten, am Anfang des ersten Chunks müssen wir die Adresse von \_\_DTOR\_END\_\_-12 viele Male platzieren, da av->bins\[2\] diesen Wert verwendet.
An der Adresse, an der die Adresse des zweiten Chunks mit den letzten 5 Nullen landet, muss die Adresse dieses ersten Chunks geschrieben werden, damit heap\_for\_ptr() denkt, dass ar\_ptr am Anfang des ersten Chunks liegt und av->bins\[2\] von dort abruft.
Auf diese Weise wird \_int\_free(CHUNK1, CHUNK2) aufgerufen und den Anweisungen gefolgt, um die Adresse des prev\_size von CHUNK2 in \_\_DTOR\_END\_\_ zu schreiben, der dann zum Shellcode springt.
Für die Anwendung dieser Technik sind einige weitere Anforderungen erforderlich, die das Payload etwas komplizierter machen.
Esta técnica ya no es aplicable, da fast der gleiche Patch wie bei unlink angewendet wurde. Es wird überprüft, ob die neue Adresse, auf die gezeigt wird, auch auf sich selbst zeigt.
Auf diese Weise, wenn "fb" auf die Adresse einer Funktion in der GOT gesetzt wird, wird an dieser Adresse die Adresse des überschriebenen Chunks platziert. Dafür muss die Arena in der Nähe der Adressen von dtors sein. Genauer gesagt muss av->max\_fast an der Adresse stehen, die wir überschreiben werden.
Deshalb, wenn wir in das Feld size eine Größe von 8 + NON\_MAIN\_ARENA + PREV\_INUSE setzen, wird fastbin\_index() fastbins\[-1] zurückgeben, das auf av->max\_fast zeigen wird.
In diesem Fall wird av->max\_fast die Adresse sein, die überschrieben wird (nicht die, auf die gezeigt wird, sondern diese Position wird überschrieben).
Außerdem muss der benachbarte Chunk des freigegebenen Chunks größer als 8 sein -> Da wir gesagt haben, dass die Größe des freigegebenen Chunks 8 beträgt, müssen wir in diesen falschen Chunk nur eine Größe größer als 8 setzen (da die Shellcode im freigegebenen Chunk sein wird, muss am Anfang ein jmp stehen, der in Nops fällt).
Aufgrund der Nullen von \_DTOR\_END\_ und der wenigen Adressen in der GOT sind keine dieser Adressen für das Überschreiben geeignet. Sehen wir also, wie wir Fastbin anwenden können, um den Stack anzugreifen.
Wenn wir die Größe so ändern, dass sie 16 anstelle von 8 beträgt, dann wird fastbin\_index() fastbins\[0\] zurückgeben und wir können dies nutzen, um den Stack zu überschreiben.
Die 4 Nullbytes sind erforderlich, damit der **av** auf diese Adresse zeigt und das erste Element eines **av** ist das Mutex, das den Wert 0 haben muss.
Außerdem wird in **av->system\_mem** (1484 Bytes über der Position im Stack) genügend Müll vorhanden sein, der es uns ermöglicht, die Überprüfung zu umgehen.
Außerdem muss der benachbarte Chunk des freigegebenen Chunks größer als 8 sein -> Da wir gesagt haben, dass die Größe des freigegebenen Chunks 16 beträgt, müssen wir in diesen falschen Chunk nur eine Größe größer als 8 setzen (da die Shellcode im freigegebenen Chunk sein wird, muss am Anfang ein jmp stehen, der in Nops fällt, die nach dem Feld size des neuen falschen Chunks stehen).
In diesem Fall versuchen wir, einen Zeiger auf ein malloc zu haben, der vom Angreifer veränderbar ist (zum Beispiel, dass der Zeiger im Stack unter einem möglichen Überlauf zu einer Variablen liegt).
So könnten wir diesen Zeiger auf eine beliebige Stelle zeigen lassen. Allerdings ist nicht jeder Ort gültig, die Größe des gefälschten Chunks muss kleiner als av->max\_fast und genauer gesagt gleich der angeforderten Größe bei einem zukünftigen Aufruf von malloc()+8 sein. Daher, wenn wir wissen, dass nach diesem verwundbaren Zeiger ein malloc(40) aufgerufen wird, muss die Größe des gefälschten Chunks 48 betragen.
Wenn das Programm zum Beispiel den Benutzer nach einer Zahl fragt, könnten wir 48 eingeben und den veränderbaren malloc-Zeiger auf die nächsten 4 Bytes zeigen lassen (die zum EBP gehören könnten, so dass die 48 dahinter bleibt, als ob es die Kopfgröße wäre). Außerdem muss die Adresse ptr-4+48 mehreren Bedingungen entsprechen (in diesem Fall ist ptr=EBP), das heißt, 8 <ptr-4+48<av->system\_mem.
Wenn dies zutrifft, wird beim nächsten malloc-Aufruf, den wir als malloc(40) angegeben haben, die Adresse auf den EBP gesetzt. Wenn der Angreifer auch kontrollieren kann, was in diesem malloc geschrieben wird, kann er sowohl den EBP als auch den EIP mit der gewünschten Adresse überschreiben.
Ich denke, das liegt daran, dass, wenn es freigegeben wird, free() speichert, dass an der Adresse, auf die der EBP im Stack zeigt, ein Chunk mit der perfekten Größe für das neue malloc() reserviert ist, und weist ihm diese Adresse zu.
Zuerst wird die Größe des Wilderness-Chunks mit einem sehr großen Wert (0xffffffff) überschrieben, so dass jede ausreichend große Speicheranforderung in \_int\_malloc() behandelt wird, ohne den Heap erweitern zu müssen.
Zweitens wird av->top geändert, damit es auf einen vom Angreifer kontrollierten Speicherbereich zeigt, wie den Stack. In av->top wird \&EIP - 8 platziert.
Victim erhält den Wert der Adresse des aktuellen Wilderness-Chunks (des aktuellen av->top) und remainder ist genau die Summe dieser Adresse plus der Anzahl der Bytes, die von malloc() angefordert wurden. Wenn also \&EIP-8 bei 0xbffff224 liegt und av->top 0x080c2788 enthält, dann ist die Menge, die im kontrollierten malloc reserviert werden muss, damit av->top auf $EIP-8 für das nächste malloc() zeigt:
Es ist wichtig zu wissen, dass die Größe des neuen Wilderness-Chunks größer sein muss als die Anforderung des letzten malloc(). Das heißt, wenn das Wilderness auf \&EIP-8 zeigt, wird die Größe genau im EBP-Feld des Stacks liegen.
Die freigegebenen Chunks werden je nach ihrer Größe in den Bin eingefügt. Bevor sie eingefügt werden, werden sie jedoch in unsorted bins gespeichert. Ein Chunk wird nicht sofort in seinen Bin eingefügt, sondern bleibt in unsorted bins. Wenn dann ein neuer Chunk reserviert wird und der zuvor freigegebene Chunk passt, wird er zurückgegeben, aber wenn ein größerer Chunk reserviert wird, wird der freigegebene Chunk in den entsprechenden Bin verschoben.
Um den verwundbaren Code zu erreichen, muss die Speicheranforderung größer als av->max\_fast (normalerweise 72) und kleiner als MIN\_LARGE\_SIZE (512) sein.
bck->fd = bin; Die Liste wird geschlossen, indem sie auf bin zeigt.
Es wird benötigt:
Reservieren Sie zwei mallocs, so dass das erste nach der Freigabe des zweiten überlaufen werden kann und in seinen Bin eingefügt wird (dh ein größeres malloc als das zweite Stück reserviert wird, bevor der Überlauf erfolgt).
Das Ziel ist, wenn wir einen Heap überlaufen können, der darunter ein bereits freigegebenes Stück mit seinem Bin hat, können wir seinen bk-Pointer ändern. Wenn wir seinen bk-Pointer ändern und dieses Stück das erste in der Bin-Liste wird und reserviert wird, wird der Bin getäuscht und ihm wird gesagt, dass das letzte Stück in der Liste (das nächste Angebot) an der falschen Adresse liegt, die wir angegeben haben (zum Beispiel auf den Stack oder GOT). Wenn also ein weiteres Stück reserviert wird und der Angreifer Berechtigungen dafür hat, wird ihm ein Stück an der gewünschten Position gegeben und er kann dort schreiben.
Nachdem das modifizierte Stück freigegeben wurde, muss ein größeres Stück als das freigegebene reserviert werden, damit das modifizierte Stück aus den unsortierten Bins entfernt und in seinen Bin eingefügt wird.
Der Bin muss warten, bis malloc() oft genug aufgerufen wird, damit der modifizierte Bin erneut verwendet wird und den Bin dazu bringt zu glauben, dass das nächste Stück an der falschen Adresse liegt. Dann wird das gewünschte Stück gegeben.
Um die Schwachstelle so schnell wie möglich auszunutzen, wäre ideal: Reservierung des anfälligen Stücks, Reservierung des zu modifizierenden Stücks, Freigabe dieses Stücks, Reservierung eines größeren Stücks als das zu modifizierende, Modifizierung des Stücks (Schwachstelle), Reservierung eines Stücks derselben Größe wie das verwundbare und Reservierung eines zweiten Stücks derselben Größe, das auf die gewählte Adresse zeigt.
Um diesen Angriff zu schützen, wird die typische Überprüfung verwendet, dass das Stück "nicht" falsch ist: Es wird überprüft, ob bck->fd auf victim zeigt. Das heißt, in unserem Fall, ob der fd-Pointer des falschen Stücks, das auf dem Stack zeigt, auf victim zeigt. Um diesen Schutz zu umgehen, müsste der Angreifer auf irgendeine Weise (wahrscheinlich über den Stack) in der Lage sein, die Adresse von victim an der richtigen Stelle zu schreiben. Damit es wie ein echtes Stück aussieht.
Der Angriff ist wie zuvor, dh der bk-Pointer muss geändert werden und all diese malloc()-Aufrufe sind erforderlich, aber zusätzlich muss die Größe des modifizierten Stücks so geändert werden, dass size - nb <MINSIZEist.
Zum Beispiel muss die Größe auf 1552 gesetzt werden, damit 1552 - 1544 = 8 <MINSIZE(dieSubtraktionkannnichtnegativsein,daeinunsignedWertverglichenwird).
Außerdem wurde ein Patch eingeführt, um es noch komplizierter zu machen.
Es besteht im Wesentlichen darin, so viel wie möglich Speicher für Heaps zu reservieren und diese mit einer Polsterung von Nops gefolgt von einer Shellcode zu füllen. Außerdem wird als Polsterung 0x0c verwendet. Es wird versucht, auf die Adresse 0x0c0c0c0c zu springen, und wenn also eine Adresse überschrieben wird, die mit dieser Polsterung aufgerufen wird, wird dorthin gesprungen. Im Wesentlichen besteht die Taktik darin, so viel wie möglich zu reservieren, um zu sehen, ob ein Pointer überschrieben wird, und auf 0x0c0c0c0c zu springen, in der Hoffnung, dass dort Nops vorhanden sind.
Es besteht darin, durch Reservierungen und Freigaben den Speicher so zu strukturieren, dass zwischen freien Stücken reservierte Stücke verbleiben. Der zu überlaufende Puffer wird in einem dieser Stücke platziert.
<summary><strong>Erlernen Sie AWS-Hacking von Grund auf mit</strong><ahref="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
* Wenn Sie Ihr **Unternehmen in HackTricks beworben sehen möchten** oder **HackTricks als PDF herunterladen möchten**, überprüfen Sie die [**ABONNEMENTPLÄNE**](https://github.com/sponsors/carlospolop)!
* Holen Sie sich das [**offizielle PEASS & HackTricks-Merch**](https://peass.creator-spring.com)
* **Treten Sie der** 💬 [**Discord-Gruppe**](https://discord.gg/hRep4RUj7f) oder der [**Telegramm-Gruppe**](https://t.me/peass) bei oder **folgen** Sie uns auf **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
* **Teilen Sie Ihre Hacking-Tricks, indem Sie PRs an die** [**HackTricks**](https://github.com/carlospolop/hacktricks) und [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) GitHub-Repositories einreichen.