GreHack 2016 write ups
Introduction
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:
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:
- For input lengths 0 to 2, output length of 32;
- For input lengths 3 to 18, output length of 48;
- For input lengths 19 to 34, output length of 64;
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 A
s, only the first byte of the cookie has an
impact of the second block of the output. Now, we try to encrypt 31 A
s
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 A
s 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(('192.168.4.1', 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
172.18.0.183:9874
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.
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.
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 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:172.18.0.183:9874 user@192.168.4.2 -p 8255 &
--<snip>--
echo '{"query_type":"QueryGetUsers","query_arg":1,"version":1}' | gzip \
| ncat 127.0.0.1 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')
>>> re.search(b'(GH16{.*})', b64decode(answer[0]['pub_key'])).groups()
(b'GH16{choucroute_garnie_lk398}',)
Conclusion
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!