No-exec / NX

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Informações Básicas

O bit No-Execute (NX), também conhecido como Execute Disable (XD) na terminologia Intel, é uma funcionalidade de segurança baseada em hardware projetada para mitigar os efeitos de ataques de buffer overflow. Quando implementado e ativado, ele distingue entre regiões de memória destinadas a código executável e aquelas destinadas a dados, como a stack e a heap. A ideia central é impedir que um atacante execute código malicioso via vulnerabilidades de buffer overflow — por exemplo, colocando o código malicioso na stack e redirecionando o fluxo de execução para ela.

Sistemas operacionais modernos aplicam o NX através dos atributos das tabelas de páginas que suportam os cabeçalhos de programa ELF. Por exemplo, o cabeçalho PT_GNU_STACK combinado com as propriedades GNU_PROPERTY_X86_FEATURE_1_SHSTK ou GNU_PROPERTY_X86_FEATURE_1_IBT informa ao loader se a stack deve ser RW ou RWX. Quando o NX está habilitado e o binário foi linkado com uma stack não-executável (-z noexecstack), qualquer tentativa de pivotar a execução para páginas de dados controladas pelo atacante (stack, heap, buffers mapeados via mmap, etc.) causará uma falha, a menos que essas páginas tenham sido explicitamente marcadas como executáveis.

Detectando NX rapidamente

  • checksec --file ./vuln irá mostrar NX enabled ou NX disabled com base no cabeçalho GNU_STACK.
  • readelf -W -l ./vuln | grep GNU_STACK exibe as permissões da stack; a presença de uma flag E indica que a stack é executável. Exemplo:
$ readelf -W -l ./vuln | grep GNU_STACK
GNU_STACK      0x000000 0x000000 0x000000 0x000000 0x000000 RW  0x10
  • execstack -q ./vuln (from prelink) é útil ao auditar grandes coleções de binários porque imprime X para binários que ainda têm uma stack executável.
  • Em tempo de execução, /proc/<pid>/maps mostrará se uma alocação é rwx, rw-, r-x, etc., o que é útil ao verificar JIT engines ou custom allocators.

Evasões

Primitivas de reutilização de código

É possível usar técnicas como ROP para contornar essa proteção executando trechos de código executável já presentes no binário. Cadeias típicas incluem:

  • Ret2libc
  • Ret2syscall
  • Ret2dlresolve quando o binário não importa system/execve
  • Ret2csu ou Ret2vdso para sintetizar syscalls
  • Ret2… — qualquer dispatcher que permita costurar um estado de registradores controlado com código executável existente para invocar syscalls ou gadgets de biblioteca.

O fluxo de trabalho geralmente é: (1) leak um ponteiro de code ou libc através de um info leak, (2) resolver as bases das funções, e (3) criar uma cadeia que nunca precise de bytes executáveis controlados pelo atacante.

Sigreturn Oriented Programming (SROP)

SROP constrói um sigframe falso em uma página gravável e pivota a execução para sys_rt_sigreturn (ou o equivalente ABI relevante). O kernel então “restaura” o contexto forjado, concedendo instantaneamente controle total sobre todos os registradores de uso geral, rip e eflags. Challenges recentes de CTF (por exemplo, a tarefa Hostel no n00bzCTF 2023) mostram como cadeias SROP primeiro invocam mprotect para alterar a stack para RWX, depois reutilizam a mesma stack para shellcode, contornando efetivamente o NX mesmo quando está disponível apenas um gadget syscall; ret. Consulte a página dedicada ao SROP para mais truques específicos de arquitetura.

Ret2mprotect / ret2syscall para inverter permissões

Se você pode chamar mprotect, pkey_mprotect, ou até dlopen, pode solicitar legitimamente um mapeamento executável antes de rodar shellcode. Um pequeno skeleton pwntools fica assim:

from pwn import *
elf = ELF("./vuln")
rop = ROP(elf)
rop.mprotect(elf.bss(), 0x1000, 7)
payload = flat({offset: rop.chain(), offset+len(rop.chain()): asm(shellcraft.sh())})

A mesma ideia se aplica a cadeias ret2syscall que configuram rax=__NR_mprotect, apontam rdi para uma página mmap/.bss, armazenam o comprimento desejado em rsi e definem rdx=7 (PROT_RWX). Uma vez que uma região RWX exista, a execução pode pular com segurança para bytes controlados pelo atacante.

Primitivas RWX de motores JIT e kernels

Motores JIT, interpretadores, drivers de GPU e subsistemas do kernel que emitem código dinamicamente são uma forma comum de recuperar memória executável mesmo sob políticas NX rígidas. A vulnerabilidade do kernel Linux de 2024 CVE-2024-42067 mostrou que falhas em set_memory_rox() deixaram páginas JIT do eBPF graváveis e executáveis, permitindo que atacantes injetassem gadgets ou blobs de shellcode inteiros dentro do kernel apesar das expectativas NX/W^X. Exploits que ganham controle sobre um compilador JIT (BPF, JavaScript, Lua, etc.) podem então arranjar para que seu payload viva nessas arenas RWX e só precisem de uma única sobrescrita de ponteiro de função para pular para elas.

Non-return code reuse (JOP/COP)

Se instruções ret estiverem reforçadas (por exemplo, CET/IBT) ou o binário não possuir gadgets ret expressivos, mude para Jump-Oriented Programming (JOP) ou Call-Oriented Programming (COP). Essas técnicas constroem despachadores que usam sequências jmp [reg] ou call [reg] encontradas no binário ou em bibliotecas carregadas. Elas ainda respeitam NX porque reaproveitam código executável existente, mas contornam mitigações que observam especificamente grandes cadeias de instruções ret.

ROP & JOP

Referências

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks