Hallowed be thy name

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)

TL;DR

An xor on a server's response allows us to find the flag.

Methodology

>_ 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...

claps

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

FLAG_IS:

bzhctf{The_sands_of_time_for_me_are_running_low}