Le progamme lapin.py est le suivant:
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import scrypt
from Crypto.Util.number import long_to_bytes
while True:
	pin = int(input(">>> PIN code (4 digits): "))
	if 0 < pin < 9999:
		break
flag = open("flag.txt", "rb").read()
k = scrypt(long_to_bytes(pin), b"FCSC", 32, N = 2 ** 10, r = 8, p = 1)
aes = AES.new(k, AES.MODE_GCM)
c, tag = aes.encrypt_and_digest(flag)
enc = aes.nonce + c + tag
print(enc.hex())
Un code PIN à 4 chiffres est demandé, il est utilisé pour dériver une clé de chiffrement à l’aide de la fonction scrypt, cette clé sera utilisée pour chiffrer le flag grâce à la fonction encrypt_and_digest.
La fonction encrypt_and_digest permet d’obtenir une valeur tag qui permettra de vérifier que le déchiffrement s’est bien passé grâce à la fonction verify.
Le fichier qui npous est fourni contient dans l’ordre les données suivantes :
- noncequi fait 128 bits (32 caractères hexadécimal)
- cipher
- tagqui fait 128 bits (32 caractères hexadécimal)
Les données chiffrées sont donc les données restantes quand on enlève les 32 premiers caractères (le nonce) et les 32 derniers caractères (le tag).
Comme la clé est dérivée d’un code PIN à 4 chiffres et que nous pouvons utiliser la fonction verify pour vérifier la clé de chiffrement nous allons utiliser la force brute pour déchiffrer et retrouver le flag.
Le programme suivant va nous permettre d’attaquer le fichier par force brute :
#!/usr/bin/python3
from Cryptodome.Cipher import AES
from Cryptodome.Protocol.KDF import scrypt
from Cryptodome.Util.number import long_to_bytes
output = open("output.txt").read().strip()
nonce = bytearray.fromhex(output[:32])
ciphertext = bytearray.fromhex(output[32:-32])
tag = bytearray.fromhex(output[-32:])
print(f"nonce      : {nonce.hex()}\nciphertext : {ciphertext.hex()}\ntag        : {tag.hex()}")
for pin in range(9999):
    k = scrypt(long_to_bytes(pin), b"FCSC", 32, N = 2 ** 10, r = 8, p = 1)
    aes = AES.new(k, AES.MODE_GCM, nonce=nonce)
    flag = aes.decrypt(ciphertext)
    try:
        aes.verify(tag)
        print(f"pin        : {pin}\nflag       : {flag}")
        break
    except ValueError:
        pass
Le script suivant automatise les opérations :
#!/bin/bash
set -e
if [ ! -f output.txt ]; then
    wget https://hackropole.fr/challenges/fcsc2021-crypto-lapin/public/output.txt -O output.txt
fi
time python3 029_Lapin.py
Le résultat est le suivant :
--2025-02-09 18:14:35--  https://hackropole.fr/challenges/fcsc2021-crypto-lapin/public/output.txt
Resolving hackropole.fr (hackropole.fr)... 51.91.236.193, 2001:41d0:301::28
Connecting to hackropole.fr (hackropole.fr)|51.91.236.193|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 207 [text/plain]
Saving to: ‘output.txt’
output.txt                                         100%[=======>]     207  --.-KB/s    in 0s      
2025-02-09 18:14:35 (141 MB/s) - ‘output.txt’ saved [207/207]
nonce      : f049de59cbdc9189170787b20b24f742
ciphertext : 6ccb9515e8b0250f3fc0f0c14ed7bb1d4b42c09d02fe01e0973a7233d99af55ce696f599050142759adc26796d64e0d6035f2fc39d2edb8a0797a9e45ae4cd55074cf99158d3a6
tag        : 4dc70a7e836e3b30382df30de49ba60a
pin        : 6273
flag       : b'FCSC{xxxxxxxx}\n'
real   0m29,439s
user   0m29,366s
sys	   0m0,071s
On note que l’attaque en force brute n’a pris que 29 secondes sur un modeste ordinateur portable.