Catégorie: Crytpo
Difficulté: Facile
Flag: CTF{d6bd1954527310f3f831baa46582f553a9e780d8fa747637d25da1281c24edaf}
Challenge
Description
Are you feeling down? Here is a list of exciting words for you, hope you'll feel better after this. (probably not)
Ce challenge tourne sur un docker et n'est pas disponible
Solution
Dans un chiffrement par CTR, on utilise un nonce à qui l'on ajoute un compteur et l'on chiffre le tout avec une clé. Le résultat de ce chiffrement est xoré avec le plaintext que l'on souhaite chiffrer.
Ici, le nonce et la clé sont réutilisés dans l'oracle du serveur. Ce qui veut dire qu'on peut faire une attaque par clair choisi et en déduire la valeur des blocs chiffrés utilisés dans le XOR, on va les appeler les KEYSTREAM.
De là, on peut tenter de XOR chaque chiffrés que l'on a dans ctr.txt et voir s'ils donnent quelque chose de lisible. Exemple avec le premier f24e8c4bb594b2590edc658609608f16
import socket
import string
class Client:
def __init__(self, host: str, port: int) -> None:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(1)
self.sock.connect((host, port))
def recv_line(self) -> bytes:
data = b''
while True:
b = self.sock.recv(1)
if b in [b'\n', b'']:
break
data += b
return data
def encrypt(self, data: bytes) -> bytes:
self.recv_line() # On s'en fou de cette ligne
self.sock.send(data + b'\n') # On envoie notre clair choisi à chiffrer
return bytes.fromhex(self.recv_line()[12:].decode()) # On retourne le résultat du chiffrement
def xor(a: bytes, b: bytes) -> bytes:
assert len(a) == len(b)
return bytes([a ^ b for a, b in zip(a, b)])
def get_keystreams(host, port) -> list[bytes]:
client = Client(host, port)
keystreams = []
for j in range(125):
# Attaque par clair choisi
data = b'0' * 16
cipher = client.encrypt(data)
# On connait le plaintext et le résultat, on xor les deux pour en déduire le keystream
keystream = xor(data, cipher)
keystreams.append(keystream)
return keystreams
host, port = '34.107.26.201', 30329
keystreams = get_keystreams(host, port)
encrypted = bytes.fromhex('f24e8c4bb594b2590edc658609608f16')
charset = string.printable.encode()
for i, keystream in enumerate(keystreams):
decrypted = xor(encrypted, keystream)
if all([c in charset for c in decrypted]):
print(i, decrypted.decode())
break
66 ThrillingThrilli
C'est le keystream n°66 qui a pu déchiffrer la première ligne de ctr.txt.
On sait que le flag est sous la forme CTF{sha256}, soit 69 caratères, c'est à dire le même nombre de ligne que contient ctr.txt.
On s'apperçoit aussi que l'ascii de C est 67 c'est à dire le numéro de notre bloc si l'on commence à compter à partir de 1 et non 0.
Il suffit donc de faire la même opération pour toutes les lignes de ctr.txt et regarder le numéro du keystream qui les déchiffre.
import socket
import string
class Client:
def __init__(self, host: str, port: int) -> None:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(1)
self.sock.connect((host, port))
def recv_line(self) -> bytes:
data = b''
while True:
b = self.sock.recv(1)
if b in [b'\n', b'']:
break
data += b
return data
def encrypt(self, data: bytes) -> bytes:
self.recv_line() # On s'en fou de cette ligne
self.sock.send(data + b'\n') # On envoie notre clair choisi à chiffrer
return bytes.fromhex(self.recv_line()[12:].decode()) # On retourne le résultat du chiffrement
def xor(a: bytes, b: bytes) -> bytes:
assert len(a) == len(b)
return bytes([a ^ b for a, b in zip(a, b)])
def get_keystreams(host, port) -> list[bytes]:
client = Client(host, port)
keystreams = []
for j in range(125):
# Attaque par clair choisi
data = b'0' * 16
cipher = client.encrypt(data)
# On connait le plaintext et le résultat, on xor les deux pour en déduire le keystream
keystream = xor(data, cipher)
keystreams.append(keystream)
return keystreams
def main() -> None:
host, port = '34.107.26.201', 30329
keystreams = get_keystreams(host, port)
with open('ctr.txt', 'r') as f:
encrypted_words = [bytes.fromhex(line) for line in f.read().splitlines()]
flag = ''
charset = string.printable.encode()
for encrypted in encrypted_words:
for i, key in enumerate(keystreams):
decrypted = xor(key, encrypted)
if all([c in charset for c in decrypted]):
keystream_n = i+1
flag += chr(keystream_n)
print(keystream_n, decrypted.decode())
print(flag)
if __name__ == '__main__':
main()