GreHack 2017 write ups
Introduction
The 2017 edition of the GreHack security conference was yesterday! Just like last year, I took part in the onsite CTF competition for the Greunion team.
Bellow are the write ups for the challenges I solved.
Crypto 100
In this challenge, we have a custom implementation of DSA, where the random
value usually noted as k
is not random at all: it is derived from the message
to sign. Here is what the challenge description says:
I think I did a major breakthrough in cryptography: I managed to implement the DSA algorithm without any need of a random source. After all, all we need is a nonce, right ?
Don't you dare tell me it's insecure: I tested it!
Well, yes it is insecure, and in fact it is a special case of the cryptopals crypto challenge number 43.
We are given the following:
- The public key
p
,q
,g
,y
; - A message
m
with its signatures1
,s2
; - A message
m_new
to sign.
Since we can compute k=nonce(m)
, we can retrieve the secret key x
and
compute the signature.
Since the Python Crypto.PublicKey
does not have a DSA.importKey
method, we
use the following command to retrieve the public key components:
$ sed 's/DSA //g' public.pem | openssl dsa -pubin -text
And here is the Python code to sign the new message:
from Crypto.Hash import SHA
from gmpy2 import invert
# insert here `nonce` code from my_DSA
q = 0xfc89d1aaf8a856b283525b8e58abd036696d2a87
g = 0x544d234fc6b7e05ca78d147f1100b336b6e8c38c...
p = 0x8000000000000002dd00e2e801235c9fec970079...
with open('signed') as f:
signed = f.read()
k = nonce(map(ord, signed), q)
h = long(SHA.new(signed).hexdigest(), 16)
with open('signature') as f:
s1, s2 = eval(f.read())
with open('sign_me') as f:
h_new = long(SHA.new(f.read()).hexdigest(), 16)
x_times_r = (s2 * k - h) % q
s2_new = (invert(k, q) * (h_new + x_times_r)) % q
print '({}, {})'.format(s1, s2_new)
All is left to do is to submit the signature to the validation server, and get the flag back.
Crypto 300
In this challenge, we have an RSA ciphertext that we need to decrypt. To do so, we have an online service which is an oracle. Indeed, we send a ciphertext, and it tells us if the corresponding plaintext is odd or even.
This is the cryptopals crypto challenge number 46 and a StackExchange post tells us how to solve this challenge.
We extract the RSA modulus with the following command:
$ openssl rsa -pubin -text -in public.pem
We just need to implement this methode into code:
from socket import socket
n = 0xd985bbdf2022392cf4e1ce8d67ca1137a85e9299...
cipher = 0x66b588a90a92d2fb365a647508618e30994...
class Oracle(object):
def __init__(self, ip, port):
self.s = socket()
self.s.connect((ip, port))
self.buf = ''
def read_until(self, end):
while True:
try:
pos = self.buf.index(end)
except ValueError:
pos = -1
if pos >= 0:
data = self.buf[:pos]
self.buf = self.buf[pos + len(end):]
return data
self.buf += self.s.recv(1024)
def is_even(self, c):
self.read_until('Ciphertext:\n')
self.s.sendall('{}\n'.format(hex(c)[2:].rstrip('L')))
data = self.read_until('\n')
if 'plaintext is even' in data:
return True
if 'odd plaintext' in data:
return False
raise ValueError(data)
def solve(self, cipher, n):
two_e = 2 ** 65537
low, high = 0, n
while low != high:
cipher = (two_e * cipher) % n
if self.is_even(cipher):
high = (low + high) / 2
else:
low = (low + high) / 2
return low
flag = Oracle('192.168.4.30', 4200).solve(cipher, n)
print hex(flag)[2:].rstrip('L').decode('hex')
Hardware 100
This challenge is unusual: we have a picture of a LCD wired to some kind of microcontroller and displaying the beginning of the flag:
The challenge descriptions says that the LCD is not wide enough to display the full flag, but we have a waveform of the signals between the board and the LCD controller. Let's fire up GTKWave and see what these signals are looking like:
So we have what looks like an enable signal EN
, and 7 other lines. Let's
report the values of these lines on an online emulator of the LCD
controller:
This is the second character of the display (the first one being a space). We
don't need to reproduce the characters mapping from the LCD controller
documentation, since in our case it follows the ASCII encoding: 0b01100110
is
a f
.
Instead, we write a quick parser for the wave file, which extracts the values of the lines when the enable signal is set. This prints us the full flag:
vals = {}
flag = ''
with open('vcd') as f:
for l in f:
a, b = l.strip().split()
vals[b[1]] = int(b[0])
if vals.get('(') and int(a[1:]) > 44000000: # `(` is EN signal
flag += chr(int(''.join(str(vals[c]) for c in '\'&%$#"!'), 2))
print flag
Conclusion
As always, doing a CTF is quite fun! Thanks again to the Greunion team for having me onboard, this time we ended up taking the 4th place!
Also, two of those crypto challenges were taken from the cryptopals challenges, maybe doing the other ones as preparation for future CTFs would not be stupid…