Category: Cryptography
Points: 300
Description: Nous avons récupéré les sources d'un étrange service de chiffrement... IP: ctf.bzh port 11000
Files: sources.py (file missing at the beginning of the ctf)
An xor on a server's response allows us to find the flag.
>_ cat sources.py
import sys
import random
import base64
import socket
from threading import *
FLAG = "bzhctf{REDACTED}"
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
init_seed = random.randint(0,65535)
class client(Thread):
def __init__(self, socket, address):
Thread.__init__(self)
self.sock = socket
self.addr = address
self.start()
def get_keystream(self, r, length):
r2 = random.Random()
seed = r.randint(0, 65535)
r2.seed(seed)
mask = ''
for i in range(length):
mask += chr(r2.randint(0, 255))
return mask
def xor(self, a, b):
cipher = ''
for i in range(len(a)):
cipher += chr(ord(a[i]) ^ ord(b[i]))
return base64.b64encode(cipher)
def run(self):
r = random.Random()
r.seed(init_seed)
self.sock.send(b'Welcome to the Cipherizator !\n1 : Enter plain, we give you the cipher\n2 : Need a flag ?\n3 : Exit')
while 1:
self.sock.send(b'\n>>> ')
response = self.sock.recv(2).decode().strip()
if response == "1":
self.sock.send(b'\nEnter plain : ')
plain = self.sock.recv(1024).decode().strip()
mask = self.get_keystream(r, len(plain))
self.sock.send(b'Your secret : %s' % self.xor(mask, plain))
elif response == "2":
mask = self.get_keystream(r, len(FLAG))
self.sock.send(b'Your secret : %s' % self.xor(mask, FLAG))
elif response == "3":
self.sock.close()
break
if __name__ == "__main__":
if len(sys.argv) != 2:
print("usage: %s port" % sys.argv[0])
sys.exit(1)
serversocket.bind(('0.0.0.0', int(sys.argv[1])))
serversocket.listen(5)
print ('server started and listening')
while 1:
clientsocket, address = serversocket.accept()
print("new client : %s" % clientsocket)
client(clientsocket, address)
When we open a connection with the server via netcat, we've got the following response:
Welcome to the Cipherizator !
1 : Enter plain, we give you the cipher
2 : Need a flag ?
3 : Exit
>>>
The first option ask us to send a string that we've got ciphered.
But when we send the same string after reconnecting to the server we get the same answer. (interesting!)
The second option returns the flag with a different key on each call.
Here too, when we stop the connection and reconnect, we notice that the different flags always appear in the same order, but only the first 10. (very interesting!!!)
Until then, no source code....
By doing tests I try to send a string that starts with 'bzhctf{'
And the beginning of the returned string corresponds to the one received after a first call to option 2. (very very interesting!!!!!)
Start of the string received after sending 'bzhctf{aaaaaa...}' (directly after the connection)
rFkCYKnX
String received during a call to option 2 (also directly after the connection)
rFkCYKnXhChVUmjXdcxA+xhJAqlNDtiKuwvSdMcQv2vs9XOwDPvuxrLMkUFCEGNz
From there the source code was revealed.
I've deduced from that it's a xor...
So we're going to attempt an attack by key reuse.
An explanation of the principle can be found here.
For that, we send a string with 'a' ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
The server sends us back 'r0ILYrzQnh1cVlbFdcNF6SZHBZdYBtSOhQzcZ/kcu1Xs5neOH+/hybrDl39PHnVv'
So we have:
message | key | cipher |
---|---|---|
m1 | k | c1 |
m2 | k | c2 |
with:
m1 = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
m2 = bzhctf{?
k = m1 xor c1
c1 = r0ILYrzQnh1cVlbFdcNF6SZHBZdYBtSOhQzcZ/kcu1Xs5neOH+/hybrDl39PHnVv
c2 = rFkCYKnXhChVUmjXdcxA+xhJAqlNDtiKuwvSdMcQv2vs9XOwDPvuxrLMkUFCEGNz
We can recover m2 with k xor c2.
Let's do this with python !
>_ cat xor.py
#!/usr/bin/env python3
import base64
alphabet = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','_','#', '!', '}', '{']
fl = "bzhctf{"
#bzhctf{The_sands_of_time_for_me_are_running_low}
def xor_strings(s, t):
"""xor two strings together"""
if isinstance(s, str):
# Text strings contain single characters
return b"".join(chr(ord(a) ^ ord(b)) for a, b in zip(s, t))
else:
# Python 3 bytes objects contain integer values in the range 0-255
return bytes([a ^ b for a, b in zip(s, t)])
if __name__ == "__main__":
cipherText = "rFkCYKnXhChVUmjXdcxA+xhJAqlNDtiKuwvSdMcQv2vs9XOwDPvuxrLMkUFCEGNz"
cipherText = base64.b64decode(cipherText)
m = bytearray("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 'utf-8') # plain input
c1 = base64.b64decode("r0ILYrzQnh1cVlbFdcNF6SZHBZdYBtSOhQzcZ/kcu1Xs5neOH+/hybrDl39PHnVv") # cipher output
keys = list(xor_strings(c1, m))
while 1:
for i in alphabet:
message = list(xor_strings(cipherText, bytearray(fl+i+"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 'utf-8')))
if (keys[len(fl)] == message[len(fl)]):
print(i)
fl = fl+i
print(fl)
#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
#r0ILYrzQnh1cVlbFdcNF6SZHBZdYBtSOhQzcZ/kcu1Xs5neOH+/hybrDl39PHnVv
>_ ./xor.py
T
bzhctf{T
h
bzhctf{Th
e
bzhctf{The
_
bzhctf{The_
s
bzhctf{The_s
a
bzhctf{The_sa
n
bzhctf{The_san
d
bzhctf{The_sand
s
bzhctf{The_sands
_
bzhctf{The_sands_
o
bzhctf{The_sands_o
f
bzhctf{The_sands_of
_
bzhctf{The_sands_of_
t
bzhctf{The_sands_of_t
i
bzhctf{The_sands_of_ti
m
bzhctf{The_sands_of_tim
e
bzhctf{The_sands_of_time
_
bzhctf{The_sands_of_time_
f
bzhctf{The_sands_of_time_f
o
bzhctf{The_sands_of_time_fo
r
bzhctf{The_sands_of_time_for
_
bzhctf{The_sands_of_time_for_
m
bzhctf{The_sands_of_time_for_m
e
bzhctf{The_sands_of_time_for_me
_
bzhctf{The_sands_of_time_for_me_
a
bzhctf{The_sands_of_time_for_me_a
r
bzhctf{The_sands_of_time_for_me_ar
e
bzhctf{The_sands_of_time_for_me_are
_
bzhctf{The_sands_of_time_for_me_are_
r
bzhctf{The_sands_of_time_for_me_are_r
u
bzhctf{The_sands_of_time_for_me_are_ru
n
bzhctf{The_sands_of_time_for_me_are_run
n
bzhctf{The_sands_of_time_for_me_are_runn
i
bzhctf{The_sands_of_time_for_me_are_runni
n
bzhctf{The_sands_of_time_for_me_are_runnin
g
bzhctf{The_sands_of_time_for_me_are_running
_
bzhctf{The_sands_of_time_for_me_are_running_
l
bzhctf{The_sands_of_time_for_me_are_running_l
o
bzhctf{The_sands_of_time_for_me_are_running_lo
w
bzhctf{The_sands_of_time_for_me_are_running_low
}
bzhctf{The_sands_of_time_for_me_are_running_low}
Traceback (most recent call last):
File "./xor.py", line 37, in <module>
if (keys[len(fl)] == message[len(fl)]):
IndexError: list index out of range
bzhctf{The_sands_of_time_for_me_are_running_low}