Points: 100 (Saw this-1), 400 (Saw this-2)
Survive and get the flag!
Note: This challenge contains two flags, one of them is easier to fetch, the other is harder.
The easier flag will be clearly indicated as "Flag 1", the harder flag as "Flag 2"
nc 18.104.22.168 31337
64 bit ELF. Lauch it with the almighty IDA Pro and press the powerful F5 key, we'll find that the service first ask us to input our user name and a lucky number. Then, it will use
srand to set up the random number seed, and generate a random number sequence.
For Saw this-1, we'll have to try to guess all the random numbers. By investigating the memory, we found that our
user_name start from
0x603108, and the
seed variable's at
0x603148. Since it let us input at most 64 characters, we can leak the
seed by input a
user_name which length's 64 characters long. After we leak the
seed, we can enter a
lucky_number which cause the line
srand(seed + lucky_number); into
srand(0). This will cause the server always generate a same group of random numbers, and so we can easily beat the game and get the flag1.
For Saw this-2, we'll have to try to get a shell. The above pseudo code show us that there's a format string vulnerability in the program. It seems that the
format variable is hard-coded in the program, we can't control it directly. But it's on the stack, so if we can find a way to overwrite the
format pointer, we'll be able to trigger the format string vulnerability.
It seems our only chance is to control the content of
j is big enough, we'll be able to overwrite all the vairables on the stack, even the return address. We found that
v8 (the variable holds the iteration number) is exactly at
v7. So we'll have to try to make
v8 >= 17, which is, making the return value of
(signed int)floor(gen_rand_double() * 13.0 + 4.0) = 17.
For random numbers, the only variable we can control is
lucky_number. We'll have to find a proper number
lucky_number, and let
srand(N) set the right random number seed so
(signed int)floor(gen_rand_double() * 13.0 + 4.0) will be equal to 17. And so I wrote a C program to achieve the requirement:
initstate(0, statebuf, 8) is required to get the correct number, since the original binary has the exact same line of code in the initial process. Finally, we get
So now everytime we send a
lucky_number, it has to be
v8 is set to 17 now, whenever we send the 17th number,
v8 will be modify to the number we send, which means we can control the iteration numbers, and so are the variables on the stack.
Here are some important variables we'll have to modify (overwrite):
v8. The iteration number
v10. Overwrite it to 1 so we won't lose the game.
format. Modify the pointer to the
user_name buffer's address (
0x603108). Now we can trigger the format string vulnerability.
Notice that other important variables (ex.
luck_number) will have to remain the same value after you overwrite the stack. As for the format string payload, at first I try to use %n to write the memory, until I found that the service won't let us input our user name for second time. Since we can't reuse the vulnerability, I decided to leak the stack canary and the return address(which returns to
Now we have the stack canary and the libc address. We can use the libc address to calculate the libc's base address, and get the address of
pop rdi, ret gadget and
/bin/sh string pointer. After we have all the information, we can overwrite the stack canary and the return address to launch a return-2-libc attack.
To sum up, here's the exploitation of Saw this-2:
1. Brute-Force the
srand seed so
v8(the iteration number) can be set to
v8), so we can overwrite the variables on the stack.
format to the
user_name buffer so we can trigger the format string vulnerability.
4. Leak the stack canary & return address.
5. Use the return adress to calculate libc's base address, so we can get
pop rdi, ret &
6. Overwrite the stack canary & return address to launch the return-2-libc attack.
And Finally, we get the flag2:
Brain cancer? LOL indeed, but it's still a great challenge!