seed-sPRiNG
4 minutos de lectura
Se nos proporciona un binario de 32 bits llamado seed_spring
:
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Ingeniería inversa
Esta vez no tenemos el código fuente. Sin embargo, podemos utilizar una herramienta de ingeniería inversa (reversing) como Ghidra para descompilar el binario y obtener código en C más o menos legible. Esta es la función main
:
void main() {
uint local_20;
uint local_1c;
uint local_18;
int local_14;
undefined *local_10;
local_10 = &stack0x00000004;
puts("");
puts("...");
puts("");
puts("Welcome! The game is easy: you jump on a sPRiNG.");
puts("How high will you fly?");
puts("");
fflush(stdout);
local_18 = time((time_t *) 0x0);
srand(local_18);
local_14 = 1;
while(true) {
if (0x1e < local_14) {
puts("Congratulation! You\'ve won! Here is your flag:\n");
fflush(stdout);
get_flag();
fflush(stdout);
return 0;
}
printf("LEVEL (%d/30)\n", local_14);
puts("");
local_1c = rand();
local_1c = local_1c & 0xf;
printf("Guess the height: ");
fflush(stdout);
__isoc99_scanf("%d", &local_20);
fflush(stdin);
if (local_1c != local_20) break;
local_14 = local_14 + 1;
}
puts("WRONG! Sorry, better luck next time!");
fflush(stdout);
/* WARNING: Subroutine does not return */
exit(-1);
}
Lo que hace el programa es configurar una semilla para un Generador de Números Pseudo Aleatorios (PRNG en inglés) y llamarlo 30 veces. La operación AND con 0xf
se aplica al valor devuelto por rand
, y después el programa comprueba si la entrada del usuario es igual al resultado de la operación AND. Si el resultado es correcto, pasamos al siguiente nivel. Y si no, el programa termina.
Desarrollo del exploit
Una manera de saber los valores aleatorios exactos que se van a calcular es utilizar el mismo PRNG e inicializarlo con la misma semilla. Para ese propósito, podemos construir un simple programa en C:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
int i;
srand(time(0));
for (i = 0; i < 30; i++) {
printf("%d\n", rand() & 0xf);
}
return 0;
}
Si lo compilamos y lo ejecutamos, veremos 30 valores aleatorios generados:
$ gcc -o prng prng.c
$ ./prng
10
6
3
14
14
0
10
8
1
13
1
10
5
0
2
15
1
9
4
12
3
7
4
5
0
15
14
15
7
14
Y si ahora introducimos este resultado con entrada al binario seed_spring
, ganaremos el juego:
$ ./prng | ./seed_spring
# mmmmm mmmmm " mm m mmm
mmm mmm mmm mmm# mmm # "# # "# mmm #"m # m" "
# " #" # #" # #" "# # " #mmm#" #mmmm" # # #m # # mm
"""m #"""" #"""" # # """m # # "m # # # # # #
"mmm" "#mm" "#mm" "#m## "mmm" # # " mm#mm # ## "mmm"
Welcome! The game is easy: you jump on a sPRiNG.
How high will you fly?
LEVEL (1/30)
Guess the height: LEVEL (2/30)
Guess the height: LEVEL (3/30)
...
Guess the height: LEVEL (29/30)
Guess the height: LEVEL (30/30)
Guess the height: Congratulation! You've won! Here is your flag:
cat: flag.txt: No such file or directory
Genial, el programa está intentando ejecutar cat flag.txt
(falla porque no tenemos un archivo llamado flag.txt
).
Flag
Vamos a realizar lo mismo en la instancia remota a ver si nos da la flag:
$ ./prng | nc jupiter.challenges.picoctf.org 35856
# mmmmm mmmmm " mm m mmm
mmm mmm mmm mmm# mmm # "# # "# mmm #"m # m" "
# " #" # #" # #" "# # " #mmm#" #mmmm" # # #m # # mm
"""m #"""" #"""" # # """m # # "m # # # # # #
"mmm" "#mm" "#mm" "#m## "mmm" # # " mm#mm # ## "mmm"
Welcome! The game is easy: you jump on a sPRiNG.
How high will you fly?
LEVEL (1/30)
Guess the height: LEVEL (2/30)
Guess the height: LEVEL (3/30)
...
Guess the height: LEVEL (29/30)
Guess the height: LEVEL (30/30)
Guess the height: Congratulation! You've won! Here is your flag:
picoCTF{pseudo_random_number_generator_not_so_random_5308efc8}
El código para el PRNG se puede encontrar aquí: prng.c
.