Because in the end, what does matter is having fun.

CSAW 2014: Saturn Walkthrough

Sep24th, 20149:20 pm

Starring

Superkojiman as My brain runs assembly code

Barrebas as The silent disassembler ninja

Swappage as The mumbler and random guesser

Hello hello, Swappage here writing on behalf of the whole group that worked on this exploit dev :) please don’t kill me as my English is really terrible, although it might also be Koji and Bas’ fault for not reviewing this doc properly before publishing :p

During the past weekend me and a bunch of dudes from VulnHub decided to test ourselves and play the CSAW 2014 CTF challenge. Along wight he 300 point Forensics challenge, Saturn at exploitation 400 was one of the more interesting ones to solve. This is our writeup on it.

the question from the challenge stated:

You have stolen the checking program for the CSAW Challenge-Response-Authentication-Protocol system. Unfortunately you forgot to grab the challenge-response keygen algorithm (libchallengeresponse.so). Can you still manage to bypass the secure system and read the flag?

So basically our objective was to find a way to bypass the challenge-response handshake authentication process handled by this binary to read the flag; it was also obvious that we were missing a component, which was supposed to handle the task of generating the challenge response, and that we needed to live with it.

Getting the binary to run

A quick check against the binary using ldd confirmed that we actually were missing a module which was needed for the application to run; relying simply on static analysis might be extremely frustrating and unproductive, so our first step was to make sure we could execute the binary locally to also perform dynamic analysis.
We came up with the following code snippet that we compiled as shared library:

They were nothing but a dummy function but it was enough to be able to run the binary without it terminating.

The application structure

By doing some static analysis we determined that saturn was made of three main parts:

one responsible for providing the client a challenge

one responsible for checking the response sent by the client to the server

and the last one that would print out the flag if the response from the client was correct.

The access to these 3 branches was handled by something similar to a case switch, where the layout of the buffer sent by the client was verified for a sequence of commands as follows:

if the first byte was in the range of 0xa0 to 0xaf the execution flow would get into the function responsible for providing the challenge to the client

if the first byte was in the range of 0xe0 to 0xef the execution flow would get into the function responsible for checking the response

if the first byte was a 0x80, the execution flow would get into the function responsible for printing the flag to stdout.

At this point we knew that intended way to interact with the binary was to

send the command sequence to request a challenge

send the response

send the command to receive the flag

THe genChallenge() function

Yes, i named it this way, in fact the binary was stripped, so while debugging using IDA i decided to rename it for making things easier :D
By the way…
The second step was to closely verify how the function responsible for providing the challenge to the client actually worked

We figured out that the challenge was read and returned to the client from a memory location controlled by the second digit of the byte, which meant that if we sent \xa0 we received 4 bytes, while if we were sending \xa1 we were receiving 4 other bytes.
This Part required a lot of testing and analysis by talking also to the real server, in fact we didn’t have the library that would generate the challenge/response, and therefore these memory locations were all 0.

After a couple of trial and error, and thanks to superkojiman’s smartness, we figured out that we could send a sequence of commands and read up to a total of 32 bytes of memory, by sending \xa0\xa1\xa2… and so on. (more on this later, as this is really important).

At this point we thought then, that the challenge, wasn’t composed of 4 bytes, but probably by 32.

The checkResponse() function

The check response function was the one responsible for actually verifying the validity of the response provided by the client.

To access this branch of code the client had to send the proper command, in the range of 0xe0 to 0xef followed by a sequence of 4 bytes representing (part) of the response.

Again, we missed the library responsible for generating the challenge response, so everything was 0 to us yet we could figure out that

if the bytes were correct the memory location at address dword_804A0A0[eax4]* was set to 1

if the bytes were wrong exit() was called instead causing the application to terminate.

At this point that was all we knew, as we were still missing an important part of the puzzle, which comes into play when the function that is supposed to finally open and read the flag for us is called.

A matter of cycling

We thought we were really close to solving the puzzle, but we were obviously proved wrong.

If we take a look at the graph of the function responsible of opening the flag.txt file and then writing it to stdout, we can notice that there is a funny and evil function which i decided to name Cycles()

Apparantly it looks like that depending on the return value of that function, we would or wouldn’t be able to read the flag.

Let’s give a quick look at the function

The concept is as simple as this:

the function cycles 8 times using the address pointed by ebp+var_4 as counter

at first it zeroes out EAX

then it moves the value from the memory location at address dword_804A0A0[eax*4 into EAX

and it multiplies EAX by EBP+var_8 (which is always 1)

at the end of the 8 iterations it returns the value in EAX

So, considering that to get to read the flag, the only way to do that was for this function to return 1, that meant that EAX had to be 1 after the 8 cycles ended
At this point the only way, was to have dword_804A0A0[eax*4 to contain 1.

But wait, where did i see this address before? it looks familiar…

If we get back to the function that checks the response (the one accessed by sending \xeN) we notice that the memory location is exactly the same

In the end, the purpose of this function then, was to perform a further check and see if the whole response was correct.. wait, what? the WHOLE? (more on this later)

How we saw the light at the end of the tunnel

At this point everything was starting to make sense, but we were missing a point..
then all of a sudden we began to mumble about those 8 iterations

8 iterations.. 8 iterations… but what if?…

And that’s how a simple guessing can lead to the solution, what if we needed to send 8 chunks of 4 bytes and build a whole response?
we thought, then that bitwise AND would make sense, we were able to get a total of 32 bytes of challenge from the server, maybe it’s expecting us to send it 32 bytes, in chunk of 4.

we quickly tried that out by building a buffer that would at first pull 32 bytes of challenge 4 bytes at a time

and punched it to the server.. DAMN, no luck, it didn’t work!
yet we were so close..

Then at a certain point, Barrebas, who was sitting silent working on reversing the binary said…

“Wait.. the response is checked starting at a memory location that is 32 bytes away from where the challenge is read from”

We saw the light! :D
we remembered that we could send commands from \xa0 to \xaf, which probably meant we could read past the 32 bytes of the challenge… what if we tried to verify with \xe0 the output from \xa8, and all the way onward to \xe7 with \xaf?

That would have probably sent the expected response to the server for each challenge request.. we tried and..
BAM! we got the flag!