seed-sPRiNG
4 minutes to read
We are given a 32-bit binary called seed_spring
:
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Reverse engineering
This time we do not have the source code. However, we can use a reversing tool like Ghidra to decompile the binary into somewhat readable C source code. Here we have the main
function:
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);
}
What the program does is set a seed for a Pseudo Random Number Generator (PRNG) and then call rand
30 times. The AND operation with 0xf
will be applied to the value returned from rand
, and after that the program checks if the user input is equal to the result of the AND operation. If the result is correct, we continue to the next level. If not, the program exits.
Exploit development
One way of knowing the exact random values that will be computed is to create the same PRNG and initialize it with the same seed. For that purpose, we can write a simple C program:
#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;
}
If we compile it and run it, we will see 30 generated random values:
$ 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
If we pipe this output to the seed_spring
binary as input, we will win the game:
$ ./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
Nice, the program is trying to execute cat flag.txt
(it fails because we do not have a file called flag.txt
).
Flag
Let’s do the same on the remote instance and see if it returns the 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}
The code for the PRNG can be found here: prng.c
.