GreHack 2018 write ups


I was at the GreHack security conference these past days, and my team, Greunion, travelled to France so that we can play together in the on-site CTF. Big up for them!

As usual, I focused on cryptography challenges, and you will find the write-ups for the challenged I solved below.

I would like to mention that this is the first CTF where a separate category for challenges mixing reverse engineer and cryptography was listed. Usually, you could find cryptography challenges in almost any other category (most notably: web and reverse engineering). I'm not sure about the need to create such a new category, but it was unusual enough to be mentioned.

Finally, I did not manage to solve the ransomware-like challenge, but from what saw it was close enough to a real ransomware, while at the same time being simple enough to be able to be solved during the CTF. And definitively a mix of reverse engineering was needed before starting working on the cryptography part, so it made sense to be in that new category.

Keccak is not always better (crypto 250)

TL;DR: reverse the order of deterministic steps.

This challenge starts with a pretty long description, telling us about an HMAC function, and how it was used to sign session cookies on a website. Our aim is to recover the secret key, in order to be able to sign any cookie we want and get admin privileges on the server.

Except that there is no web server involved in the challenge, instead we are only given a valid signature for a single session cookie. Oh and the HMAC function is not really an HMAC, but some custom code based on Keccak (known to be chosen for SHA-3).

Anyway, we have some C++ code that performs the HMAC, a good pointer on how to link that code against a given library. After looking into it, it does something like the following:

flag = '...' # 48 bytes message = 'the-cookie-with-some-padding' # 48 bytes state0 = flag + '\x00' * 2 state1 = keccakF(state0) state2 = xor(state1, message + '\x00' * 2) state3 = keccakF(state2) hmac = state3[:48]

With keccakF been taken from the library, but as far as we are concerned we can be seen as some deterministic function.

Without looking too far, it is easy to see that the keecakF function is in fact a permutation, so it is bijective and all the steps can be reversed to get the flag. In fact, the library implements the reverse permutation, so there is not much to do. We are only missing the last 2 bytes to go from the output hmac to the state3, so let's just bruteforce them:

#include "Keccak-f.h" void bruteforce(UINT8 a, UINT8 b) { KeccakF keccakF(400); // end state: dc6b4bd3... UINT8 state[50] = {220,107,75,211,82,66,218,84,62,202,18,101,66,5,193, 27,188,206,118,225,73,98,59,56,127,205,148,18,214,61,0,46,122,141, 195,211,162,215,125,189,115,102,104,249,179,20,208,32, a, b}; // "username=Nics,admin=false" followed by \x01 and null bytes UINT8 message[50] = {117,115,101,114,110,97,109,101,61,78,105,99,115, 44,97,100,109,105,110,61,102,97,108,115,101,1,0,0,0,0,0,0,0,0,0,0, 0,0, 0,0,0,0,0,0,0,0,0,0}; // reverse the steps keccakF.inverse(state); for (int i = 0; i < 48; i++) { state[i] ^= message[i]; } keccakF.inverse(state); // check initial state if(state[48] || state[49]) { return; } // print initial state for (int i = 0; i < 50; i++) { printf("%02x", state[i]); } printf("\n"); } int main() { for(int a = 0; a < 256; a++) { for(int b = 0; b < 256; b++) { bruteforce(a, b); } } return 0; }

Let's compile and run this code to get the flag:

$ g++ -Wall solve.cpp -IKeccakTools/Sources KeccakTools/bin/Keccak-f.o -o solve $ ./solve | xxd -r -p | strings -n 48 GH18{Sponge_with_not_enough_capacity-->bad_idea}

Backdoored DH (crypto 300)

TL;DR: recover pre-master key when DHE's P is a product of two primes, use wireshark to decrypt the TLS 1.2 capture.

In this challenge, we are given a network capture of a TLS connexion to decrypt. To help us, we also have the server code as well as a secret file containing to primes (p and q) and their product (n), RSA-style.

Reading the server code, we can see that the dhparam.pem file used is custom, but we don't have it. However, we can open the network capture, and we notice:

After looking online for know backdoor in Diffie-Hellman exchanges, we find out that it is exactly the situation described in the fourth section of this paper (page 8). By performing the attack, we are able to recover the shared secret (i.e. the pre-master secret) out of the Diffie-Hellman exchanges.

Then, we can add it in Wireshark to be able to decrypt the network capture. To do so, we use the following resources:

And of course, because TLS version 1.2 is used, a special version of the PRF function has to be used as well (RFC 5246 and not RFC 2246).

After a lot of determination, we are able to create a key log file able to decrypt the network capture in Wireshark:

from Crypto.Util.number import long_to_bytes from tlslite.mathtls import PRF_1_2 from sage.all import GF, crt # get inputs with open('s3cr3t.txt') as f: exec( # p, q, n g = 2 gx = 0x7e56137f...0154aa0c gy = 0xb2fcf1b2...97673045 client_random = 0x835ee473...ea2a1234 server_random = 0x9a6c56b8...e71ebae4 # find g**(x*y) i.e. pre-master secret a1 = GF(p)(gx).log(2) # discrete log mod p a2 = GF(q)(gx).log(2) # discrete log mod q x = crt(a1, a2, p - 1, q - 1) # may not be real x... gxy = pow(gy, x, n) # ... but still real g**(x*y) # compute prf (TLS 1.2 is used) master_secret = str(PRF_1_2( long_to_bytes(gxy), # pre-master secret 'master secret', long_to_bytes(client_random) + long_to_bytes(server_random), 48 )) # wireshark compatible output print 'CLIENT_RANDOM {} {}'.format( long_to_bytes(client_random).encode('hex'), master_secret.encode('hex'))

Wireshark decrypting TLS

Even after all the time alternating between despair and trying harder, we were the first team to score that flag… first blood yeah!

HashMcQueen (re-crypto 400)

TL;DR: integer overflow.

This is a remote challenge for which we have the binary. Fortunately for us, the binary is quite easy to reverse with only a few functions not being obfuscated in any way.

In a loop, we provide:

And on each iteration, the SHA-256 of the provided string starting from the provided position (up to strlen of the remaining) is output back.

But before all that, an initialisation function reads the flag and put it in memory.

Without going into more reversing the binary, we could imagine that our attack will look like the following:

To bypass the check against the minus character, we use a simple integer overflow on the atoi function.

And instead of trying to find the exact position of the flag, we start from -1 downwards until we obtain the SHA-256 hash of the } character (the last char of the flag).

Creating the input:

# for i in xrange(0x100000000, 0x100000000 - 1337, -1): print 'h:{}'.format(i)

Filtering / bruteforcing the output:

# from hashlib import sha256 flag = '}' # avoid false-positives while True: print flag h = raw_input().strip().lower() for c in map(chr, xrange(128)): if sha256(c + flag).hexdigest() == h: flag = c + flag break

Chaining the pre and post processings with some bash-fu:

$ ./ | nc ... | egrep -o '[0-9A-F]{64}' | ./ --<snip>-- 18{4t_l3457_17_w45_n0t_crypt0} H18{4t_l3457_17_w45_n0t_crypt0} GH18{4t_l3457_17_w45_n0t_crypt0}

For a challenge in the re-crypto category, I must say that there was not much crypto in it, by I can be missing something (also: see flag value).

Note: when submitting this flag, the organisers asked me if I feel like it was worth 400 points, and indeed it was quite easy for a 400 points flag. Especially comparing it to the previous challenge which was worth 300 points…


It was a real pleasure to be able to attend the GreHack, and thanks to my teamates we were able to get the sweet victory with the first place!

GreHack 2018 scoreboard

As I'm writing these words on the train back home after a all-nighter, I must say that I am still quite happy about that!

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

Short URL: