GreHack 2016 write ups


Last week, I went to the GreHack and took part in the onsite CTF competition, for the Greek Greunion team.

Bellow are the write ups for the three challenges I solved.

Web 50

Going on the linked webpage, you are being greeted with a goat as well as two numbers: 5785 and 7033.


Looking at these numbers online, you quickly find the associated RFC. RFC 7033 introduces WebFinger that allows to discover information. The well-known part for WebFinger is /.well-known/webfinger, so we try to access it, and we are rewarded with the flag:

Reaching WebFinger

Crypto 100 (the TCP server one)

For this challenge, we are interacting with a remote TCP server. We send a string of our choice (in hexadecimal), and receive an other string in hexadecimal.

We figure out that the server encrypts our string. However, if we look at the length of the output, we have some interesting results:

From this, we can guess that the encryption is using a block cipher, with a cipher block of length 16 bytes.

Moreover, using 0 or 2 bytes as the input gives an output of length 32 bytes (two blocks), but using 3 bytes as the input gives an output of length 48 bytes (three blocks). This means that 32 - 3 = 29 bytes are added to our input before the encryption. Let's call those bytes the cookie.

Continuing our analysis, we find that the mode of operation has some kind of feedback (maybe CBC), and that the cookie is actually added at the end of our inputs.

From there, we can perform an attack close to the padding oracle attack to recover the cookie.

Indeed, if we use an input of 31 As, only the first byte of the cookie has an impact of the second block of the output. Now, we try to encrypt 31 As followed by a \x00, or by a \x01, etc. Out of the 256 ciphertexts we receive, if we look only at the second block, one of them will match the second block of the ciphertext previously gathered (the one using the first byte of the cookie). This mean that we have the first byte of the cookie at this point.

Now, we use 30 As followed by the first byte of the cookie, and we do the same operation. This allows us to get the second byte of the cookie.

Byte after byte we can recover the whole cookie, which contains the flag!

The following Python code was used:

#!/usr/bin/env python2 from socket import socket class Solution(): def __init__(self): self.s = socket() self.s.connect(('', 3100)) self.s.recv(4096) # skip greeting message def get_cipher(self, x): self.s.sendall(x.encode('hex') + '\r\n') data = self.s.recv(4096).split('\n')[0] cipher = data.strip().decode('hex') assert cipher return cipher def get_cookie(self): cookie = '' for nb_bytes in range(29): align = 'A' * (32 - nb_bytes - 1) target_block = self.get_cipher(align)[16:32] for i in range(256): x = align + cookie + chr(i) if self.get_cipher(x)[16:32] == target_block: cookie += chr(i) break else: raise ValueError('Not found') return cookie print Solution().get_cookie()

Forensics 300

We are given an SSH access to a machine targeted by some malware. After connecting to it, a ps command shows that the client binary is running with as argument. To make sure that there is indeed some network traffic to this host, we run tcpdump for some time before downloading the PCAP file to our machine and opening it with Wireshark.

Wireshark analysis

The TCP connections to that IP address and port is used to send 5 messages in a row to the server, and the server answers every message in between. The protocol seems to be based on JSON, but there are some non printable bytes involved.

Binwalk analysis

As seen in the screenshot above, running binwalk on the messages shows that they are in fact compressed with gzip.

So we have all the information we need to understand the protocol. Unfortunately, in all messages the query_type key is QueryGetTargets. We would like to get some information about the bot owner instead. But maybe the client binary has some other commands built inside? Let's look at the strings of that binary:

Strings of the binary

$ strings client | grep QueryGetTargets corrupt deflate stream:query_typequery_argversionQueryGetTargetsQueryGetUsers

We see that the string QueryGetTargets is immediately followed by the string QueryGetUsers, so it seems like a good idea to try that one.

To communicate directly with the server, we simply use our SSH connexion to create a tunnel.

ssh -L 9874: user@ -p 8255 & --<snip>-- echo '{"query_type":"QueryGetUsers","query_arg":1,"version":1}' | gzip \ | ncat 9874 | zcat > answer

The answer of the server is a big JSON string, so we use Python to understand it. The first user is seems to be the owner, and has a huge public key. Finally, we find the flag hidden in the decoded version of the public key:

>>> answer = json.loads(open('answer').read()) >>> type(answer) <class 'list'> >>> answer[0].keys() dict_keys(['pub_key', 'credit', 'utype', 'contact', 'tokens_per_target', 'id']) >>> answer[0]['utype'], answer[0]['contact'] ('owner', 'admin@cloudbot0dfkj938e5k.onion') >>>'(GH16{.*})', b64decode(answer[0]['pub_key'])).groups() (b'GH16{choucroute_garnie_lk398}',)


It had been more than a year since my last CTF, so it is good to see that even if my skills are a little bit rusty, I was still able to score a few flags! I really enjoyed the crypto challenge, which reminded me of another one from la Nuit du Hack 2014.

Thank you to the Greunion team for having me onboard, I hope to be able to do more in the future!

This article, its images and source code are released under the CC BY-SA licence.

Short URL: