ctr
Description
Write Up: Tyron
Créateur: cr0minus
Difficulté: easy
Points: 255
Format du flag: CTF{sha256}
Enoncé
- Français: Vous avez le cafard ? Voici une liste de mots excitants pour vous, en espérant que vous vous sentirez mieux après ça. (probablement pas)
- English: Are you feeling down? Here is a list of exciting words for you, hope you'll feel better after this. (probably not)
Pièce(s) jointe(s):
- https://raw.githubusercontent.com/wepfen/writeups/refs/heads/main/defcampCTF/2024/ctr/ctr.txt
Solution détaillée
Pour ce challenge on peut intéragir avec un serveur avec netcat nc IP PORT qui nous demande de lui soumettre de au plus 16 caractères!
En envoyant 'a' j'obtiens : 02308264a4b8dc1a27520cbae8854516
Et en réenvoyant 'a' j'obtiens : cc528ba18b1c9089064d80148680e30e
Et quand je relance le serveur, je réobtiens les memes résultats.
Ceci est du au fait que les messages en clair sont chiffrés avec AES CTR, ainsi pour une meme instance un message au différents chiffrés.
Cependant, on utilise un nonce pour que sur différentes connexions, le chiffré change. Ici ce n'est pas le cas et le nonce est réutilisé.
En regardant ces schémas, on comprends que la clé utilisée pour chiffrer et la meme que pour déchiffrer.
Mais également que le texte chiffré est obtenu après un XOR de la clé avec le texte clair.
Ainsi, en envoyant au serveur un texte clair composé d'octets nuls, la clé nous est directement envoyée puis ensuite tenter de xorer avec un texte chiffré du fichier ctr.txt.
Le premier est f24e8c4bb594b2590edc658609608f16.
Spoiler : ca fonctionne.
Après un peu de recherche on remarque qu'il faut continuer d'envoyer des requetes au serveur jusqu'à que ca donne un message qui s'affiche correctement.
Ainsi je déchiffre tous les messages avec ce code:
from pwn import remote, context, xor
host = "35.198.191.122"
port = 31082
context.log_level = 'CRITICAL'
def decrypt_words():
conn = remote(host, port)
words = open("ctr.txt", 'r').read().split("\n")
words = [bytes.fromhex(word) for word in words]
plaintext = b"\x00"*16
recovered = []
i = 0
words_offset = 0
while True:
i += 1
conn.recvuntilS(b'bs\n')
conn.sendline(plaintext)
keystream = bytes.fromhex(conn.recvS().split()[3])
try:
pt = xor(keystream, words[words_offset]).decode()
print(f"recovered {pt}")
recovered.append(pt)
print(f"{i=}, word number {words_offset}")
words_offset += 1
conn = remote(host, port)
except Exception as e:
continue
if len(words) == words_offset:
break
Pour résumer, je bruteforce le serveur et affiche le message déchiffré dès que possible, et affiche également le compteur pour lequel le message a été déchiffré correctement et reset la connexion avec le serveur et ainsi de suite.
Les voici :
ThrillingThrilli
ExhilaratingExhi
ElectrifyingElec
EuphoricEuphoric
JubilantJubilant
EcstaticEcstatic
[...]
High-spiritedHig
HypedHypedHypedH
JoyousJoyousJoyo
JubilantJubilant
Spoiler : ca sert à rien
Pour récupérer il faut interpréter les valeurs du compteurs qu'on a affiché avec le script précédent et les afficher en tant que code ASCII.
Voici mon script qui permet ça:
from pwn import remote, context, xor
host = "35.198.191.122"
port = 31082
context.log_level = 'CRITICAL'
def decrypt_words():
conn = remote(host, port)
words = open("ctr.txt", 'r').read().split("\n")
words = [bytes.fromhex(word) for word in words]
plaintext = b"\x00"*16
recovered = []
counter_offsets = []
i = 0
words_offset = 0
while True:
i += 1
conn.recvuntilS(b'bs\n')
conn.sendline(plaintext)
keystream = bytes.fromhex(conn.recvS().split()[3])
try:
pt = xor(keystream, words[words_offset]).decode()
print(f"recovered {pt}")
recovered.append(pt)
print(f"{i=}, word number {words_offset}")
words_offset += 1
counter_offsets.append(i)
i = 0
conn = remote(host, port)
except Exception as e:
continue
if len(words) == words_offset:
break
flag = ''.join(map(chr, counter_offsets))
print(f'{flag=}')
with open('decrypted.txt', 'w') as f:
for dec in recovered:
f.write(dec+"\n")
if __name__ == '__main__':
decrypt_words()
FLAG: CTF{d6bd195452731...}
Retex
Je n'ai pas réussi à résoudre le challenge pendant l'évènement mais j'ai pu mieux comprendre mon erreur. Le challenge comportait plusieurs phases où il fallait deviner quoi faire ce qui peut etre frustrant.
Toutefois, on a pas besoin d'aller chercher trop loin pour avancer et ne pas se jeter dans des rabbit holes.
Par exemple pour récupérer le flag il suffisait de logger tous les compteurs valides, au départ je ne loggais pas correctement et le compteur s'incrémentait sans fin au lieu de se réinitialiser (skill issue).
Lien(s) utile(s)
- https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)