Brandon T. Elliott

home

CTF Writeup: picoCTF 2023 - "Tic-Tac"

The CTF

picoCTF 2023 took place from March, 14th, 2023 to March 28th, 2023.

The Challenge

This binary exploitation challenge began with the following description:

tic-tac

After ssh’ing into my challenge instance, running an ls showed the following files were in our home directory:

ls

The Binary

The primary file of interest is the txtreader binary which takes an argument of a file name, of which the binary will print the contents of if the user owns the file, as the challenge description stated.

txtreader

Of course, just supplying the flag.txt file will not work, since we don’t own it.

The Source

The next file to take a look at is src.cpp which presumably is the source code for the txtreader binary.

src

Upon inspecting the source code, it appears that the binary checks the ownership information using the stat() function.

My first thought for solving the challenge was to create a symbolic link to the flag.txt file and supply the symbolic link as an argument to the txtreader binary.

This should work… right?

first-try

No. But we own the symbolic link…

symlink-perms

So, why doesn’t it work?

The Stat() Function

A quick search for the stat() function gives us the answer as to why.

If the file is a symbolic link, the stat() function first follows the symbolic link, and then returns the file info only after the path has been resolved.

Apparently, if the lstat() function had been used instead, our attempt would have worked, as lstat() would’ve returned the file info for the symbolic link.

TOCTOU

Going back to the challenge description, there was an interesting tag for the challenge, which was toctou.

Reading up on this, we see that this stands for “time-of-check to time-of-use”.

This is a race condition that appears to exist in the txtreader binary due to the fact that it first checks whether the user owns the file, and then uses the results of that check in order to read the contents of the file.

Although it may be only milliseconds in length, there is a time period in between those two actions taking place, which could allow us to modify the state of the file at precisely the right time window, and subsequently be able to read files that we don’t own.

Exploitation

Setting the stage for our exploit, first we will need to continuously create a symbolic link to a file that we do own. Then, we need to immediately change the symbolic link to a file which we want to read that we don’t necessarily own (in this case, obviously we are interested in the flag.txt file).

Although this could be achieved in a myriad of ways, for simplicity’s sake, I chose to use a bash one-liner for this:

timeout 30s bash -c 'while true; do ln -sf src.cpp flag; ln -sf flag.txt flag; done' &

I chose to use a while true loop for simplicity and wrap it in a timeout 30s command so that the loop will end after 30 seconds no matter what, which should give us more than enough time. Placing the & at the end will send it to the background and will allow us to simultaneously run the second part of our exploit from within the same shell.

Inside the loop, since we already know we can read the src.cpp file, we will first create a symbolic link to this file and call it flag.

Next, it will forcefully create a new symbolic link to flag.txt and will also call it flag.

In a nutshell, this loop will repeatedly create and modify the flag symbolic link, switching between a file we own, and a file we don’t.

While this is running in the background, we will simultaneously run the second part of our exploit, which is also a bash one-liner:

while ! ./txtreader flag 2> /dev/null | grep "picoCTF"; do :; done

The second part will also run a while loop, and will attempt to run ./txtreader flag while sending errors to /dev/null and grepping for picoCTF (which would indicate the flag was successfully printed out). When the flag is printed out, the loop will end.

Putting both pieces together, the underlying idea here is that eventually the timing will line up so that the binary will first check whether we own the src.cpp file, which we do, and then the symbolic link will be modified to point to the flag.txt file in time before the binary reads the file, and subsequently it will print out the flag.

Nothing left to do now but to run both parts of our exploit, and wait for the timing to be just right.

flag

As seen in the screenshot above, the flag was successfully printed out after only a second or two. We can now do a fg and Ctrl+C to stop the first part of our exploit as that’s no longer needed (although it will stop after 30 seconds no matter what).