Rogdham

TU CTF 2017 write ups

Introduction

What can you do when a team member asks for help with cryto challenges a Sunday evening? Nothing, right? No choice but to join the battle!

This is how I joined the TU CTF being called from my team, Greunion.

Crypto 100

The first step is to connect to the TCP server and explore the choices we are given. This step does not take a lot of time, since only one option works, which gives us a PCAP file (base64 encoded).

Opening the network capture with Wireshark shows an SSH connexion, a DNS query and answer, and that's it. Except that there are some base64 encoded data at the end of one of the packets:

Base64 blob in a packet

In fact, there are two base64 parts:

After some trial and error, we figured out that the ciphertext is encrypted with DES in CBC mode, and the key is obtained by decrypting enc_key with RSA.

Since n is only 79 bits long, it is not difficult to factor it, or we can just use factordb.com.

Putting everything together reveals the flag:

import gmpy2 import struct from Crypto.PublicKey import RSA from Crypto.Cipher import DES keys = ( 'ZW5jX2tleToxMzA1NTIzMDU0MDQyNjY0NTk2NDg0OTQKbjo1NDI4MDA1NzMzODAw' 'ODQ4MjY5MTAzODEKZTo2NTUzNwppdjpkqmNUCELPqQ==' ).decode('base64') cipher = ( 'bncFlPPa6T6JFup6dMYQn7m3uWokRWYT3K/j907seyUm8Pk19ZD9a5hgPZ/P8w0i' 'txAyMZNyG7dOtINeCVhSxw==' ).decode('base64') enc_key, n, e, iv = [k.split(':')[1] for k in keys.split()] enc_key, n, e = map(long, (enc_key, n, e)) # http://www.factordb.com/index.php?query=542800573380084826910381 p = 201559811875263582217 q = 2693 d = long(gmpy2.invert(e, (p - 1) * (q - 1))) rsa_key = RSA.construct((n, e, d)) des_key = struct.pack('>Q', rsa_key.decrypt(enc_key)) print DES.new(des_key, DES.MODE_CBC, iv).decrypt(cipher)

Crypto 300

This challenge comes with a network capture. Once again we fire Wireshark and extract the data sent via TCP: it is some Python code base64 encoded.

This code does the following:

Looking at the values of the RSA public key, we find that the exponent e is only 3. This means that given a timestamp t, we can obtain (t+offset)^3 % n.

To do our attack, we start by getting the following:

Now, if we name a=t+offset, we can compute (mod n):

From there it is easy to recover a+1=(x-2*y)/6 (once again, mod n). And with the variable a we have everything to send to the server and recover the flag!

from pwn import * from gmpy2 import invert from time import sleep import random n = 14259292378283788958805781028007440773742364391604066886972... with remote('cryptoclock.tuctf.com', 1230) as r: enc_times = [] # get 3 encrypted (timestamp + offset) while len(enc_times) != 3: r.recvuntil(':') r.sendline('1') r.recvuntil('RSA!\n') enc_t = long(r.recvuntil('\n')) log.info('Got enc_time {}'.format(enc_t)) if enc_t not in enc_times: enc_times.append(enc_t) sleep(0.1) # I guess it would be ok to just wait 1sec enc_t0, enc_t1, enc_t2 = enc_times # compute t1 x = enc_t1 - enc_t0 y = enc_t2 - enc_t0 six_a_plus_one = y - 2 * x a_plus_one = long((six_a_plus_one * invert(6, n)) % n) log.info('a + 1 is {}'.format(a_plus_one)) # send answer time = a_plus_one + 1 # we are at third timestamp log.info('using time {}'.format(time)) random.seed(time) guessing_int = random.randint(0, 999999999999) log.info('guessing_int {}'.format(guessing_int)) r.recvuntil(':') r.sendline('2') r.recvuntil(':') r.sendline(str(guessing_int)) print r.recvall() # prints the flag

Conclusion

I did not plan to solve some CTF challenges this weekend, but oh well, that's always fun to do!

Thanks to my teammates that were playing since the start of the CTF, we managed to solve all challenges but one and get the 6th place!

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

Short URL: http://r.rogdham.net/32.