Twin Keys
Description
Write Up: offpath
Créateur: ciphr
Difficulté: Medium - Hard
Points: 100
Format du flag: crypto{flag}
Enoncé
Le coffre-fort sécurisé de Cryptohack nécessite deux clés pour déverrouiller son secret. Cependant, Jack et Hyperreality ne se souviennent pas des clés, seulement du début de l'une d'entre elles. Pouvez-vous aider à retrouver les clés perdues pour déverrouiller le coffre-fort ?
Cryptohack's secure safe requires two keys to unlock its secret. However, Jack and Hyperreality can't remember the keys, only the start of one of them. Can you help find the lost keys to unlock the safe?
Solution détaillée
On a un code server et un socket de connexion.
import os
import random
from Crypto.Hash import MD5
from utils import listener
KEY_START = b"CryptoHack Secure Safe"
FLAG = b"crypto{????????????????????????????}"
def xor(a, b):
assert len(a) == len(b)
return bytes(x ^ y for x, y in zip(a, b))
class SecureSafe:
def __init__(self):
self.magic1 = os.urandom(16)
self.magic2 = os.urandom(16)
self.keys = {}
def insert_key(self, key):
if len(self.keys) >= 2:
return {"error": "All keyholes are already occupied"}
if key in self.keys:
return {"error": "This key is already inserted"}
self.keys[key] = 0
if key.startswith(KEY_START):
self.keys[key] = 1
return {"msg": f"Key inserted"}
def unlock(self):
if len(self.keys) < 2:
return {"error": "Missing keys"}
if sum(self.keys.values()) != 1:
return {"error": "Invalid keys"}
hashes = []
for k in self.keys.keys():
hashes.append(MD5.new(k).digest())
# Encrypting the hashes with secure quad-grade XOR encryption
# Using different randomized magic numbers to prevent the hashes
# from ever being equal
h1 = hashes[0]
h2 = hashes[1]
for i in range(2, 2**(random.randint(2, 10))):
# h1 = xor(self.magic1, h1)
h1 = xor(self.magic1, xor(h2, xor(xor(h2, xor(h1, h2)), h2)))
# h2 = xor(self.magic2, h2)
h2 = xor(xor(xor(h1, xor(xor(h2, h1), h1)), h1), self.magic2)
assert h1 != bytes(bytearray(16))
if h1 == h2:
return {"msg": f"The safe clicks and the door opens. Amongst its secrets you find a flag: {FLAG}"}
return {"error": "The keys does not match"}
class Challenge():
def __init__(self):
self.securesafe = SecureSafe()
self.before_input = "Can you help find our lost keys to unlock the safe?\n"
def challenge(self, your_input):
if not 'option' in your_input:
return {"error": "You must send an option to this server"}
elif your_input['option'] == 'insert_key':
key = bytes.fromhex(your_input["key"])
return self.securesafe.insert_key(key)
elif your_input['option'] == 'unlock':
return self.securesafe.unlock()
else:
return {"error": "Invalid option"}
listener.start_server(port=13397)
Ce challenge nous présente un coffre-fort sécurisé qui nécessite l'insertion de deux clés pour être déverrouillé. Les clés doivent satisfaire certaines conditions, et une fois insérées correctement, elles permettent d'obtenir le drapeau.
Aperçu du Code Serveur
Le code serveur utilise une méthode XOR complexe pour vérifier si les clés insérées sont correctes. Voici un aperçu des principales fonctionnalités :
- Insertion de Clés (
insert_keyoption) :- Le serveur accepte une clé sous forme de chaîne hexadécimale.
- La clé est ajoutée à un dictionnaire
self.keyssi elle n'est pas déjà présente et s'il y a moins de deux clés insérées. - Si la clé commence par le préfixe
KEY_START, une valeur spéciale lui est assignée dans le dictionnaire.
- Déverrouillage du Coffre-Fort (
unlockoption) :- Le serveur vérifie si deux clés ont été insérées.
- Il vérifie que la somme des valeurs assignées aux clés est égale à 1.
- Les clés sont ensuite hachées en utilisant MD5, et une série de transformations XOR est appliquée pour garantir que les hachages sont différents.
- Si les hachages finaux sont identiques, le coffre-fort est déverrouillé et le drapeau est révélé.
Solution
La solution repose sur la création de deux valeurs de clé différentes qui produisent des hachages MD5 identiques. Voici comment nous pouvons exploiter cela :
- Étape 1 : Générer des Clés avec une Collision MD5
- Nous devons trouver deux valeurs qui, une fois hachées avec MD5, produisent le même résultat. Cela est possible grâce à une attaque de collision sur MD5.
- Étape 2 : Insérer les Clés dans le Coffre-Fort
- Nous insérons les deux clés générées dans le coffre-fort.
- Étape 3 : Déverrouiller le Coffre-Fort
- Une fois les clés insérées, nous envoyons une requête de déverrouillage pour obtenir le drapeau.
Voici le code de la solution :
import json
import socket
import random
from Crypto.Hash import MD5
import os
def xor(a, b):
return bytes(x ^ y for x, y in zip(a, b))
# Prefix
prefix = b"CryptoHack Secure Safe"
# values created by hashcmlash with the prefix "CryptoHack Secure Safe "
a_bytes = "4372 7970 746f 4861 636b 2053 6563 7572 6520 5361 6665 2020 2758 bf67 92f3 e5a6 9858 528e 3461 9b9e 1445 97fb 8e73 ad1d 9fa1 8b9c 8f5a 7b50 42da 26f3 b430 4315 9871 7af5 b683 355d 7561 7ead 2c33 b53f 68f7 6bf1 90f9 6373 82da 60d6 5043 da46 8c27 9ff7 f980 5c3a e85b fc56 2cae 16ee e27c 1e14 e8b8 ad87 9128 acbc 369e 953f"
b_bytes = "4372 7970 746f 4861 636c 2053 6563 7572 6520 5361 6665 2020 2758 bf67 92f3 e5a6 9858 528e 3461 9b9e 1445 97fb 8e73 ad1d 9fa1 8b9c 8f5a 7b50 42da 26f3 b430 4315 9871 7af5 b683 355d 7560 7ead 2c33 b53f 68f7 6bf1 90f9 6373 82da 60d6 5043 da46 8c27 9ff7 f980 5c3a e85b fc56 2cae 16ee e27c 1e14 e8b8 ad87 9128 acbc 369e 953f"
a_bytes = bytes.fromhex(a_bytes.replace(" ", ""))
b_bytes = bytes.fromhex(b_bytes.replace(" ", ""))
# test if values are different
assert a_bytes != b_bytes, "Values are the same: {} == {}".format(a_bytes.hex(), a_bytes.hex())
hash1 = MD5.new(a_bytes).digest()
hash2 = MD5.new(b_bytes).digest()
# test collision
assert hash1 == hash2, "Hashes do not match: {} != {}".format(hash1.hex(), hash2.hex())
# test if xor does not change the result
magic1 = os.urandom(16)
magic2 = os.urandom(16)
for i in range(2, 2**(random.randint(2, 10))):
hash1 = xor(magic1, xor(hash2, xor(xor(hash2, xor(hash1, hash2)), hash2)))
hash2 = xor(xor(xor(hash1, xor(xor(hash2, hash1), hash1)), hash1), magic2)
assert hash1 != bytes(bytearray(16))
assert hash1 == hash2, "Hashes do not match after being xored: {} != {}".format(hash1.hex(), hash2.hex())
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("socket.cryptohack.org", 13397))
print(s.recv(1024).decode())
# Insert first value
payload1 = {"option": "insert_key", "key": a_bytes.hex()}
s.send(json.dumps(payload1).encode())
response1 = s.recv(1024).decode()
print("Response for key 1:", response1)
# Insert second value
payload2 = {"option": "insert_key", "key": b_bytes.hex()}
s.send(json.dumps(payload2).encode())
response2 = s.recv(1024).decode()
print("Response for key 2:", response2)
# get the flag
payload3 = {"option": "unlock"}
s.send(json.dumps(payload3).encode())
response3 = s.recv(1024).decode()
print("Unlock attempt:", response3)
s.close()
if __name__ == "__main__":
main()