The clue for this one is again brief,
It's all about who you know and what you want. access_control_server_f380fcad6e9b2cdb3c73c651824222dc.quals.shallweplayaga.me:17069
With the clue is a binary file which you can find archived here.
First up I just connected to the server with netcat to see what it looked like:
Ok neat, no idea what it wants yet but check out that "Connection ID". I bet that's useful later right?
Let's examine the binary:
Standard stuff. There's a version string "version 3.11.54" so that's needed to answer the question to the server I bet.
Let's try running the client...
Ok time for more reversing... In IDA we find this tidbit quickly:
So the "message" is probably "hack the world"? Back to the client:
Alrighty, thats cool. We get logged in, we see a user list, and we see theres some kind of key although we don't have privs to get the key yet. The client is not interactive either, so we can't try more stuff. It's looking like we need to build our own client... So we need to reverse how this one works so we can do that.
Let's look at the network layer to watch what it sends:
Ok all normal, we got:
- the version number string (as expected)
- the username
- the two commands "list users" and "print key".
- What's up with the password though. Is that the user password?
I capture a few connections and the response to the password prompt changes each time. So this is somehow ciphered. So if we're going to write a client, we need to encrypt the password correctly...
Back to IDA! We find this code naming two functions "sub_8048EAB" and "sub_8048F67":
sub_8048EAB XORs the first 5 bytes of the given plaintext password with a key. The key is based on the connection ID (dword_804B04C) and an offset into the connection ID string is chosen based on (dword_804BC80 % 3):
So what do we know, in plain language:
- The ciphered password string really can only be 5 characters long
- The key (connection ID) is public and given to us at the start of the connection
- The offset of the connection ID where the key begins is possible to calculate by deriving how dword_804BC80 is generated then computing it modulo 3.
The first two are easy, the third item requires more reversing. When you think about it, what is the point of reversing more here? So we know exactly the offset for the key? Does it matter? Let's try something in the server with netcat:
So the server, for 1 connection ID gives us multiple attempts at the password. So since the correct offset modulo 3 can only ever be 0,1,2,3 we have only a maximum of 4 password attempts before we will get the right password.
Long story short, I didn't bother reversing that bit and moved on to the second function that operates on the password before sending it:
This function is simpler, it just parses the output of the first function looking for characters <= 31 and if found, adds 32 to them. If the character == 127 it does other things.
Again I got lazy here, I don't care about the edge case single character == 127 so I completely ignored implementing that!
For both of these "optimizations" I made up front, I paid a price though. We'll see that later :)
So now I have the makings of a client that can login, let's try it:
Cool it works! And now I'm interactive I can try more commands. Except there aren't any! Poop. Well let's try these other users eh? Hope they all picked silly passwords like Mr Grumpy did.
Ok fine. We go on this way for a while. Finally we get to the user "duchess":
Ok neat. The user duchess can get the key, but first they must answer the challenge!
The challenge looks to be a 5 byte string, encrypted in the same way our password was when it was sent over the wire.
If we get the answer wrong we get dropped, but we don't lose our connection. We just have to relogin. That's pretty important because we keep our connection ID.
So this is the code I came up with as a "test" client to solve the challenge. I didn't continue coding past this point because my first attempt got me the flag:
Here's what it looks like when run.
To be fair, this client is not complete. The logic which selects the real correct answer is not in place. It was only by luck that my solution finds the correct answer some times. I have rerun the exploit about 10 times now and it solved it correctly 3 times out of those 10 attempts.
Since we're not about perfect execution, just about CTF, we leave it open ended but happy we solved it in time.