Write-up: To be RNG or to PRNG, that is the question

Yay, crypto :D

Once we open the .zip file, we get two files:

-rw-r--r-- 1 neowo neowo 1980 Sep 27 15:22 crypt.c
-rw-rw-r-- 1 neowo neowo 42 Sep 27 15:19 flag.txt.encrypted

where flag.txt.encrypted is our flag, but encrypted using crypt.c

Next obvious step: we open crypt.c (how kind of the challenge creator to give us the source code), aaand its scary. Especially if you don’t know C.

The title gives us a great hint: we’re playing with PRNG, aka Pseudo-Number generators.

Quick explanation of PRNG

How do they work? Pretty simple: We know that computers cannot generate “True” randomness. So instead, we use some smart algorithms that do the something close enough: “false” randomness. A bunch of equations that will, given an original state or “seed”, produce seemingly random values. Depending on the algorithm chosen, it can be rather hard to compute the seed from a small amount of random values. All the security resides in the secrecy and entropy of the seed. If we know the seed, we can use the same algorithm to get the exact same values every time; likewise, if the seed has low entropy (aka is chosen from predictable sources), we can find it.

Most modern algorithms use CSPRNG (Cryptographically Secure (or Suitable) Pseudo-Random Number Generators". These algorithms not only use mathematical concepts making it very hard to compute the seed from the random values but also environments of high entropy to generate the seed. Those environments could be a combination of time, user mouse movements, CPU temperatures, weather data or even Cloudflare’s lava lamp wall. In conclusion, a good random number generator needs two elements:

Now let’s analyse crypt.c

It can be hard to find at first glance what the flaw is. An experimented user, however, would instantly notice these lines of code:

int main(int argc, char **argv) {
  char file_path[128];
  FILE *input_file, *output_file;

  SetSeed(time(NULL));

SetSeed(time(NULL)); ? That’s very interesting. After a quick google search, we can realize that time(NULL); returns the current system’s time as a timestamp, whereas SetSeed(); sets the seed. Remember what we said? If we can find the seed, we can use the same algorithm to generate the exact same values in the same order. So all we need, is to find the exact time the file was encrypted. But wait! Our system store’s that in the file’s metadata!

>>> stat flag.txt.encrypted 
  File: flag.txt.encrypted
  Size: 42        	Blocks: 8          IO Block: 4096   regular file
Device: 10302h/66306d	Inode: 22186554    Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1000/   neowo)   Gid: ( 1000/   neowo)
Access: 2024-09-27 15:21:13.000000000 +0100
Modify: 2024-09-27 15:19:48.000000000 +0100
Change: 2024-10-02 17:34:25.545820435 +0100
 Birth: 2024-10-02 17:34:25.545820435 +0100

But this isn’t timestamp format… Most people would use online tools to manually convert the date to timestamp, but we’re beyond that:

$ date -d "$(stat --printf=%y flag.txt.encrypted | cut -d. -f1)" +"%s"
1727446788

Now we can modify crypt.c and add our seed: 1727446788, compile then run:

$ sed -n 64,68p crypt.c 
int main(int argc, char **argv) {
  char file_path[128];
  FILE *input_file, *output_file;

  SetSeed(1727446788);
  
$ gcc crypt.c 

$ ./a.out flag.txt.encrypted 
Key used: 5aWHFANxfFtHwimgpceHzS5TOc21RYvw
File flag.txt.encrypted.encrypted has been encrypted!
DONE.

$ cat flag.txt.encrypted.encrypted 
Intake24{PrnG_1s_n0+_aS_SAFE_!5_1T_sEEM5}

Challenge solved :D