TU CTF 2017 write ups
Introduction
What can you do when a team member asks for help with crypto 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:
In fact, there are two base64 parts:
- The first one gives some parameters (
enc_key
,n
,e
,iv
); - The seconde one is some binary data.
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:
- Generate a very big random
offset
; - If we choose option 1, it adds the current timestamp to
offset
, encrypts it with RSA and sends us the result (no padding); - If we choose option 2, it adds the current timestamp to
offset
, and uses that as a seed to generate a number. If we send that same number, we get the flag.
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:
enc_t0=(t+offset)^3 % n
;enc_t1=(t+1+offset)^3 % n
after waiting one second;enc_t2=(t+2+offset)^3 % n
after waiting another second;
Now, if we name a=t+offset
, we can compute (mod n
):
x=enc_t1-enc_t0
, sox=3*a^2+3*a+1
;y=enc_t2-enc_t0
, soy=6*a^2+12*a+8
;
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!