Solution de prae_gitlab pour Clair connu

intro crypto

13 décembre 2024

Analyse

Prenons en considération les différents éléments que nous avons en notre main

  • Un ciphertext de la sorte qui est une suite hexadécimale :
d9 1b 70 23 e4 6b 46 02 f9 3a 12 02 a7 60 13 04 a7 68 11 03 fd 61 15 02 fa 68 41
  • Et le code qui a servi a créé ce ciphertext :
import os
from Crypto.Util.number import long_to_bytes
from Crypto.Util.strxor import strxor
FLAG = open("flag.txt", "rb").read()
key = os.urandom(4) * 20
c = strxor(FLAG, key[:len(FLAG)])
print(c.hex())

On comprend que la clef a été générée aléatoirement et donc totalement inconnue, mais nous savons qu’elle est d’une taille de 4 octets. Et nous savons que le plaintext est “xor"é avec la clef de 4 octets qui a été étendue (expand) pour pouvoir être utilisé sur l’ensemble du plaintext.

A partir de là, nous ne connaissons que le ciphertext…

Sauf…

Clair obscur

Sauf que nous savons également indirectement une partie du plaintext ! Le défi étant de retrouver le flag, celui-ci débute par “FCSC{” : nous savons donc que le début du plaintext sera composé des caractères ‘F’, ‘C’, ‘S’, ‘C’, ‘{’ (et ‘}’ à la fin) !

Finalement, nous en savons un peu plus dans notre enquête :

  • Le ciphertext
  • XOR
  • une partie du plaintext

Ce qu’il nous manque, c’est donc la clef.

Par chance, la clef est de 4 octets… et ‘FCSC’ fait également 4 octets (la coïncidence incroyable ! :), nous pouvons donc commencer par essayer de déterminer la clef en effectuant le processus inverse.

Nous allons prendre les 4 premières valeurs hexadecimal du ciphertext : d9 1b 70 23

  • 0xd9 (+) k1 = ‘F’ = 0x46
  • 0x1b (+) k2 = ‘C’ = 0x43
  • 0x70 (+) k3 = ‘S’ = 0x53
  • 0x23 (+) k4 = ‘C’ = 0x43

Avec ces éléments, nous pouvons effectuer un calcul inverse :

  • 0xd9 (+) 0x46 = k1
  • 0x1b (+) 0x43 = k2
  • 0x70 (+) 0x53 = k3
  • 0x23 (+) 0x43 = k4

Essayons d’implémenter cela en python pour faire simple et rapide :

from operator import xor
>>> print(hex(xor(0xd9, 0x46)))
0x9f
>>> print(hex(xor(0x1b, 0x43)))
0x58
>>> print(hex(xor(0x70, 0x53)))
0x23
>>> print(hex(xor(0x23, 0x43)))
0x60

Notre clef serait donc 9f 58 23 60 ?!

Et bien, voyons !

Nous avons gardé deux caractères plaintext ‘{’ et ‘}’ sous le coude pour pouvoir tester ! Puisque la clef est de 4 octets, le plaintext au caractère 5 sera donc chiffré de nouveau avec le premier élément de la clef (k1) donc notre 9f ! Normalement, nous devrions avoir ceci si notre clef k1 est valide :

  • c1 (+) k1 ?= '{' = 0xe4 (+) 0x9f ?= 0x7b

Voyons cela, déjà, testons la valeur hexadécimale de { :

>>> hex(ord("{"))
'0x7b'

Déjà, on est sûr que { == 0x7b, passons au test du decipher :

>>> hex(xor(0xe4, 0x9F))
'0x7b'
>>> chr(xor(0xe4, 0x9F))
'{'

Bingo ! Notre k1 est parfaitement valide, nous pouvons donc supposer que k2, k3 et k4 sont aussi valides !

En route vers le decipher !

Pour cela, nous allons appliqué notre clef 0x9f 0x58 0x23 0x60 à l’ensemble du ciphertext :

from operator import xor
k = [
    0x9F,
    0x58,
    0x23,
    0x60,
]
with open("output.txt") as file:
    index = 0
    while ciphertext := file.read(2):
        h = int(ciphertext, 16)
        print(chr(xor(h, k[index])), end="")
        index=(index+1)%4

Et si nous executons notre programme :

FCSC{3ebfb1b880d802cb96be0bb256f4239c27971310cdfd1842083fbe16b3a2dcf7}

Notre dernier caractère étant ‘}’ nous pouvons donc être relativement serein sur notre bon plaintext obtenu, c’est bien notre flag !