Sharif CTF 2016: Kiuar (Pwn 200)

Category: pwn
Points: 200
Solves: 20


telnet 12432


In this challenge you're just given a server and port to connect to, when you connect you see the following message:

Welcome to Sharif blackbox challenge :)
Proof of work: Are you ready?
Give me a 32-bit hex integer, whose leftmost 22 bits of its MD5 is 1111111111000010001010.
You have 60 seconds to reply.

Alright, so we need to find a 32-bit with an MD5 matching the provided 22-bits, and we have to feed it back to the server as hex. In order to determine the exact format the server wanted I looked at the file in the misc 200 challenge "The Impossible Game" which has a similar requirement. The code from that challenge is as follows:



answ = hex(answ)[2:]
answ = answ.zfill(ANSWER_SIZE//4)

chal = hashlib.md5(answ.encode("utf8")).hexdigest()

So from that we know that the server will be expecting the value in lowercase hex, with no "0x" prefix, and exactly 8 characters long. I went ahead and wrote some quick Python to create a list of 22-bit values and the 8 character strings they were calculated from. I then saved the list to a file so I could load it without having to recalculate it every run.

import marshal
import md5

def upper_22(s):
	return (ord(s[0]) << 14) | (ord(s[1]) << 6) | (ord(s[2]) >> 2)

hashes = [None] * 4194303 # 22-bit max

print "Calculating hashes..."
for i in range(0x10000000, 0x10F00000):
	h = hex(i)[2:]
	u = upper_22(
	if u < 4194303:
		hashes[u] = h

o = open('hashes.bin', 'wb')
marshal.dump(hashes, o)

Great, we have an easy way to get a 32-bit value whose MD5 matches what is needed! Now if we send the server the hex integer it's looking for we get the following back from the server:

OK, let's start

And then some binary data

0000000: 789c 0b4e cd4b 51a8 cc2f 2d52 080c 5248  x..N.KQ../-R..RH
0000010: ce4f 49b5 e28a cc2f 55c8 482c 4b55 3034  .OI..../U.H,KU04
0000020: 5028 4e4d cecf 4b29 5628 c957 284a 2dc8  P(NM..K)V(.W(J-.
0000030: a9d4 e302 009b f310 54                   ........T

I recognized it as zlib from the first two bytes and decompressed it to get the following:

Send your QR code:
You have 10 seconds to reply.

The server requests a QR code from you within the next 10 seconds. I tried generating a QR code of the 32-bit hex integer I had sent earlier with the Python qrcode library and sent it to the server, which responded with this:

The size of input data must be exactly 200 bytes o_O

The server demands your input be 200 bytes, no more, no less. I went ahead and set all of the size settings for my QR code to as low as possible, then padded it with bytes of 0xFF until it was at 200 bytes exactly. I sent it to the server and got the following response:

Your data is not in proper compressed format :(

So the server doesn't want a raw PNG, it wants it compressed in some way. Based on the fact that it previously send zlib data, I compressed the QR code PNG with zlib, and then padded with bytes of 0xFF until it reached 200 bytes. After sending that to the server it responded with this:

Processing the received command...

The output of your command is large, I only send 18 bytes of it :P
Sorry, command not

The server isn't complaining about the compression or the size anymore, but now it's saying out command output is too large and it's only going to give us the first 18 bytes, which say "Sorry, command not". After seeing that I realized that the data in the QR code wasn't supposed to be the 32-bit hex integer value, but instead a command for it to execute, so I sent a QR code containing "ls" and got the following:

Processing the received command...


After seeing the flag file was there in the directory I sent "cat flag"

Processing the received command...

The output of your command is large, I only send 18 bytes of it :P

The flag is cut off because it's more than 18 characters so I sent "tail -c 18 flag"

Processing the received command...


I didn't see any bytes from the start of the flag in the end of the flag, so I sent "tail -c 30 flag"

Processing the received command...

The output of your command is large, I only send 18 bytes of it :P

There it is, bytes from the start of the flag and the end of the flag! I pieced the responses together to get the full flag shown below: