Intro

Overwatch was a 909 point exploitation challenge in BCTF 2017 that was released a little after the halfway point in the competition. It was one of the more creative heap pwning challenges I’ve seen in a CTF, and I really enjoyed solving it. It required the use of multiple heap exploitation techniques and was solved by only 3 teams. Unfortunately, we did not look at this challenge until late in the game and were unable to finish our exploit in time.
It was only after the CTF ended that I managed to solve this with the help of brieflyx who gave me some additional guidance on how to get the leak and re-affirmed some of the ideas I had come up with for my exploit plan.
Although I didn’t get the flag until after the CTF finished, I thought it’d still be worth it to do a write-up for educational purposes, because the way to get the leak is actually pretty neat and demonstrates several heap exploitation techniques that I’ve been meaning to write about for a while. So without further ado, let’s get started!

Reversing

The binary is a menu-driven program that allows users to perform multiple actions for 15 rounds.

Each time a user begins a new round, 4 bytes from /dev/urandom are generated and placed in a global variable in the .BSS named randBytes. These 4 pseudorandom bytes are subsequently used to determine which map and hero gets printed out whenever the user calls option 1 or option 2 from the main menu.

We can only change our name and email once per round and this check is done by updating the changeEmail_status and changeName_status globals.

Going back to the updatePtr() function, though, can you spot the vuln?

There is a subtle single NULL byte off-by-one vulnerability that is introduced by the memcpy().
This means that if we size a heap chunk appropriately, a NULL byte can potentially overflow into the LSB of the size metadata field of an adjacent heap chunk.

Although a single NULL byte doesn’t seem like much, this error actually opens up many ptmalloc2 heap attacks, some of which we will use to exploit this program and gain code execution.

Infoleak Plan

Our first order of business to exploit this program is to bypass ASLR by getting some sort of infoleak.
Getting a leak in this program is tricky though, because while we can allocate heap chunks for our name and email, we can’t actually print out the contents of these chunks.

To make matters worse, there aren’t any additional vulnerabilities besides the off-by-one that could be abused to get an easy heap or libc leak (that I could see, anyway).

So without the ability to print out anything useful, how can we get a leak?

This is where things get interesting.

Remember the randBytes global we mentioned earlier?

Although randBytes isn’t directly printed out, it is used to determine the levels, maps, and heroes of each teammate that ARE.
If we can overwrite the data in randBytes with part of a libc address, we can use dynamic constraint solving based on the levels, maps, and heroes of each teammate that are printed out to determine the bytes we’ve overwritten randBytes with.

One heap exploitation technique that would allow us to do this, is the unsorted bin attack technique.

In an unsorted bin attack, we overwrite the BK pointer of a chunk in the unsorted bin with a controlled value, so that when this chunk is removed from the unsorted bin, it will write &unsorted_bin to BK+0x10.

For our exploit we can use this attack to overwrite the BK pointer with an address in the .BSS so that part of &unsorted_bin overwrites randBytes.

In order to be able to overwrite the BK pointer of a chunk in the unsorted bin though, we first need to produce 2 overlapping chunks. This will allow us to corrupt the contents of a free chunk without needing an overflow or a use-after-free (UAF) vulnerability!

To achieve this overlap, we can perform another heap exploitation technique called House of Einherjar.

House of Einherjar attack

House of Einherjar is a really cool heap exploitation technique that my teammate uafio taught me.

House of Einherjar works by exploiting a single NULL byte overflow to corrupt the size field of an allocated chunk and flip its prev_inuse bit off to trick it into thinking its previous chunk is free. Then, when this chunk with the corrupted size is free()‘d, because its prev_inuse bit is now off, it will attempt to perform a backwards consolidation with a fake chunk, using its prev_size field to calculate the location of this fake free chunk.

Notice that C has been backwards consolidated into the old A chunk and that it is now the new top chunk, even though B is still allocated after it!

This means that we can now allocate another chunk that overlaps with B to overwrite any data in B’s chunk!

For a more detailed explanation on how House of Einherjar works, feel free to check out uafio’s stream about the technique here and here.

Unsorted bin attack

Now that we have overlapping chunks, we will want to free B and place it in the unsorted bin. However, there are a few problems in the heap now that we need to fix in order to successfully perform this step.

The prev_inuse bit for both B @ 0x603090 and the old C chunk are now both flipped off. This is problematic, because this means it will attempt to perform additional chunk merging, which we don’t want.

Libc leak via constraint solving

Now that we’ve written a partial libc address into randBytes, we can leak it using some Z3 magic.

To do this, I added some Z3 constraints based on the outputted map, hero and level of each teammate in order to dynamically find which bytes would satisfy those constraints and produce the same output.

I then solved these constraints to discover the bytes written to randBytes, which I subsequently used to calculate a final libc base address.

Fastbin attack

Now that we have the leak, the rest of the exploit is pretty straight forward.

In order to hijack the control flow of this program, we will want to overwrite a function pointer that gets called.
We will overwrite __realloc_hook, since full RELRO is enabled, preventing us from overwriting a GOT entry.

To perform this overwrite, we can use a fastbin attack to overwrite the FD pointer of a free()‘d fast chunk in order to get malloc() to return an almost-arbitrary address of our choosing.

As I’ve mentioned in previous posts, we can abuse the fact that we can point FD to misaligned addresses to get around the size check that malloc() does.

After overwriting the FD pointer, to get malloc() to return our desired address, we need to allocate two0x68 sized chunks.

However, to do this in the program, we will need to free another chunk after allocating the first one, since after allocating the first 0x68 sized chunk, both our namePtr and emailPtr globals will be populated and so we will be unable to allocate another chunk until one of these 2 pointers is free()‘d.

The state of our unsorted bin right now is so messed up though that if we free the namePtr chunk again in its current state, it will fail a check and trigger a “corrupted unsorted chunks” error.

Thanks

Thanks again to brieflyx for his help and to vakzz for lending a second pair of eyes to look at this task.
Also to uafio and grazfather for taking away my free time by making me do pwnable.tw challenges.