Hallowed be thy name

Catégorie: Cryptographie

Points: 300

Description: Nous avons récupéré les sources d'un étrange service de chiffrement... IP: ctf.bzh port 11000

Fichiers: sources.py (fichier manquant au début du ctf)

TL;DR

Un xor sur la réponse d'un serveur nous permet de retrouver le flag.

Méthodologie

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

En ouvrant une communication avec le serveur via netcat, on obtient la réponse suivante:

Welcome to the Cipherizator !
1 : Enter plain, we give you the cipher
2 : Need a flag ?
3 : Exit
>>>

La première option nous demande d'envoyer une string que l'on reçoit chiffre.

Mais quand on envoie la même string après une reconnexion au serveur on a la même réponse. (intéressant !)

La deuxième option nous retourne le flag chiffre avec une cle différente à chaque appel.

La aussi, quand on arrête la connexion et qu'on se reconnecte on s'aperçoit que les différents flags chiffres apparaissent toujours dans le même ordre, mais seulement les 10 premiers. (très intéressant !!)

Jusque la toujours pas de code source...

En faisant des tests j'essaie d'envoyer une string qui commence par 'bzhctf{'

Et la le début de la string retournée correspond à celle qui est reçue après un premier appel à l'option 2. (très très intéressant !!!)

Début de la string reçue après l'envoi de 'bzhctf{aaaa...}' (directement après la connexion)

rFkCYKnX

String reçue lors d'un appel à l'option 2 (la aussi directement après la connexion)

rFkCYKnXhChVUmjXdcxA+xhJAqlNDtiKuwvSdMcQv2vs9XOwDPvuxrLMkUFCEGNz

A partir de la le code source a ete dévoilé.

J'en ai déduis que c'est un xor...

claps

On va donc tenter une attaque par réutilisation de cle.

Une explication du principe se trouve ici.

Pour ca, on envoie une string avec des 'a' ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')

Le serveur nous renvoie 'r0ILYrzQnh1cVlbFdcNF6SZHBZdYBtSOhQzcZ/kcu1Xs5neOH+/hybrDl39PHnVv'

Nous avons donc:

message cle cipher
m1 k c1
m2 k c2

avec:

m1 = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

m2 = bzhctf{?

k = m1 xor c1

c1 = r0ILYrzQnh1cVlbFdcNF6SZHBZdYBtSOhQzcZ/kcu1Xs5neOH+/hybrDl39PHnVv

c2 = rFkCYKnXhChVUmjXdcxA+xhJAqlNDtiKuwvSdMcQv2vs9XOwDPvuxrLMkUFCEGNz

On peut donc retrouver m2 avec k xor c2.

Ya plus qu'à faire ca avec 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}