Résolution
En regardant le code fourni, on déduit les informations suivantes :
- Le chiffré est un fichier PNG
- La fonction encrypt utilise un objet nommé Generator pour générer un flot de chiffrement qui va être xoré avec le fichier en clair
- Le générateur est initialisé avec 2000 octets avec la fonction
os.urandom
(normalement suffisante pour des applications cryptographiques):self.feed = [int.from_bytes(os.urandom(1)) for i in range(2000)]
- Pour générer un octet, le générateur applique une fonction sur son état interne de 2000 octets, renvoie le résultat de la fonction appliquée et remplace le premier octet de l’état par ce nombre calculé
De plus on dispose du début en clair du fichier flag.png.
À partir du clair et du chiffré, en faisant un XOR des deux, on peut récupérer une partie du flot généré par l’objet Generator. Puisque le clair connu fait 2293 octets, on est en capacité de récupérer l’état du générateur en faisant un XOR avec le chiffré. À partir de là on initialise un objet Generator, on initialise la variable feed
avec les 2000 premiers octets, on génénère un flot qui fait la taille du chiffré puis on fait un XOR du fichier chiffré et du flot généré (plus les 2000 premiers octets) et on récupère le fichier PNG.
Voici la solution implémentée en python3 :
# 404CTF 2024
# Challenge : Poor Random Number Generator 1,
# Authors : acmo0
from my_random import Generator
def get_blocks(data,block_size):
return [data[i:i+block_size] for i in range(0,len(data),block_size)]
def xor(b1, b2):
return bytes(a ^ b for a, b in zip(b1, b2))
def pad(data,block_size):
return data+b'\x00'*(len(data)%block_size)
def encrypt(data,block_size,generator): # fonction encrypt du fichier du challenge (pareil que déchiffrer, cf XOR)
padded_data = pad(data,block_size)
data_blocks = get_blocks(padded_data,block_size)
encrypted = b''
i = 0
for block in data_blocks:
print(round(i/len(data_blocks)*100,2),"%",i,end="\r")
rd = generator.get_random_bytes(block_size)
encrypted += xor(block,rd)
i+=1
return encrypted
clear = open("flag.png.part",'rb').read()[:2000]
enc = open("flag.png.enc",'rb').read()[:2000]
BLOCK_SIZE = 4
flag = None
feed = list(b''.join([xor(clear[i:i+4],enc[i:i+4]) for i in range(0,2000,4)])) # xor entre le clair et le chiffré -> 2000 premiers octets du flot
g = Generator()
g.feed = feed # initialise le feed avec celui récupéré
with open("flag.png.enc",'rb') as f:
flag = f.read()
with open('flag_dec.png', 'w+b') as f:
e = encrypt(flag[2000:],BLOCK_SIZE,g) # déchiffre le reste du PNG
f.write(clear[:2000]+e) # écrit le clair connu + déchiffré