Description:
1
I'm using arch btw... ¯\_(ツ)_/¯
For this challenge we are given both the compiled C file and the source code for the application.
Static Analysis
For starters, I ran the application a couple of times to ensure I knew what “normal operation” was for this program.
These first runs of the program show a very simple interface where the user is prompted for input and then that input is echoed out to the screen along with some other themed text.
After getting this baseline I had a good idea of the types of exploits that this program would be vulnerable to. I knew it was either
1
2
A.) A format string vulnerability to leak data for the canary
B.) A bufferoverflow to overwrite the canary check.
With this in mind I moved on to reviewing the source code that was given to us.
There are a few interesting things we can gather from the source code.
First, we see a C structure for holding our user input. This structure contains not only our user input, but also a canary, and a binary value called give_flag
. An interesting note while we’re looking at this is that the canary is actually a function pointer instead of the typical int value or byte array. This will be interesting to play with when we get to the dynamic analysis portion of the challenge.
Secondly, This program is vulnerable to buffer overflow and and not format string due to the lack of printf()
. Our struct array holds 32 bytes but the quotegets()
function does not check the bounds on it’s reading. This will be my main mode of exploitation going forward.
The last part of the main function shows that the call_canary
structure value is called as a function, this will be important to understanding our overflow payload. Now that we know what this program is vulnerable to, we can begin the dynamic analysis and exploit development stages.
Dynamic Analysis
I began running the program and entered 33 characters to see if it would trigger an overflow.
And it triggered a seg fault. This is a good since and points to us having control of the programs execution. After further code review I realized that 33 characters was actually overkill for the buffer overflow as the quotegets()
function appends some extra characters to the array. The actual number to cause an overflow is 18.
With the number of characters for an overflow know, I began running the program inside of GDB to see what addresses we can overwrite and if we can point it to a new function. First I ran the program with normal input to see what address is normally called and where it’s stored.
I found the canary function being stored at $rbp-0x18, or 18 bytes off of the base pointer. The normal canary value is 0x0000555555555210
. I then ran the program again with my overflow of 33 characters.
Here we can see that the canary struct value is being changed and overwritten with some of our inputted A’s and some of the extra characters that quotesgets()
is adding to the end. If we add a few more we can replace the entire pointer and control the flow of the program by pointing it at any function we want. As it stands right now the program seg faults after calling the canary function since the string we entered is not a valid function. A good first test would be to overflow the canary and replace it with the original value so that we can run the program without it crashing.
python3 -c 'import sys;sys.stdout.buffer.write(b"A"*31+b"\x10\x52\x55\x55\x55\x55\x00\x00"+b"\n")' > payload
I used this python code to help generate non-printable characters for my payload. This should overflow the canary and replace it with the original hex address. Running this in GDB and piping in the payload file should provide us with the offline testing flag.
Perfect! This is exactly what we were expecting. Now we just need to chuck this code into the server program and get the real flag… right?
Sadly, no. We don’t know the address of the normal function running on the server, and this is where we stayed stuck for the rest of the CTF. Our team tried running this exploit with a large number of different addresses thinking that it would just be some small variation on what we had locally. This proved unsuccessful and it wasn’t until I read the writeups for the challenge after the event that I was able to find a solution that worked.
After the Competition
Enter, vsyscall, Vsyscall is a very special function in that it is a kernel function and also a mapped function inside of all binary executables. Since it’s originally part of the kernel, it should have the same static address across different machines since it not stored in user space. However, since it’s also a mapped function, we are still able to access it directly from the program.
Overwriting the struct canary with 0xffffffffff600000
should allow the program to execute a virtual sys call, return, and then finish execution and give us the flag. I rant his payload against the target and it ended up working, I got the flag after the comp had ended.
ALLES!{th1s_m1ght_n0t_work_on_y0ur_syst3m_:^)}
Final Thoughts
Overall, this wasn’t my favorite challenge. I wasn’t able to get the final solution to work locally on my computer since the kernel must be laid out slightly differently and it seems like a bit of a hassle to have players install Arch Linux just to try a possible payload. Still, I was able to learn a little bit more about possible static functions in programs which is always useful during buffer overflows.