CTF - W1seGuy
Introduction
La room W1seGuy proposé par TryHackMe s’inscrit dans un théme évoquant la cryptographie, celui ci suggére l’analyse d’un fichier source.py, suite à cette analyse nous pourrons déceler la faille nous permettant de trouver les flags demandées.
Phase d’analyse du code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import random
import socketserver
import socket, os
import string
flag = open('flag.txt','r').read().strip()
def send_message(server, message):
enc = message.encode()
server.send(enc)
def setup(server, key):
flag = 'THM{thisisafakeflag}'
xored = ""
for i in range(0,len(flag)):
xored += chr(ord(flag[i]) ^ ord(key[i%len(key)]))
hex_encoded = xored.encode().hex()
return hex_encoded
def start(server):
res = ''.join(random.choices(string.ascii_letters + string.digits, k=5))
key = str(res)
hex_encoded = setup(server, key)
send_message(server, "This XOR encoded text has flag 1: " + hex_encoded + "\n")
send_message(server,"What is the encryption key? ")
key_answer = server.recv(4096).decode().strip()
try:
if key_answer == key:
send_message(server, "Congrats! That is the correct key! Here is flag 2: " + flag + "\n")
server.close()
else:
send_message(server, 'Close but no cigar' + "\n")
server.close()
except:
send_message(server, "Something went wrong. Please try again. :)\n")
server.close()
class RequestHandler(socketserver.BaseRequestHandler):
def handle(self):
start(self.request)
if __name__ == '__main__':
socketserver.ThreadingTCPServer.allow_reuse_address = True
server = socketserver.ThreadingTCPServer(('0.0.0.0', 1337), RequestHandler)
server.serve_forever()
Après analyse du fichier, une méthode attirent mon oeil :
start()setup()
Commencons par l’analyse de la fonction start() :
1
2
3
4
5
6
7
8
def start(server):
res = ''.join(random.choices(string.ascii_letters + string.digits, k=5))
key = str(res)
hex_encoded = setup(server, key)
send_message(server, "This XOR encoded text has flag 1: " + hex_encoded + "\n")
...
On peut remarquer que les deux premières instructions de cette fonction déclarent les variables res et key, générant une clé de 5 caractères composée de lettres et de chiffres. Suite à cela, elle envoie cette clé générée vers la fonction setup() afin de pouvoir l’utiliser pour chiffrer en XOR le flag recherché.
** EXPLICATIOPN CRYPTAGE XOR**
Le cryptage XOR est un système de cryptage basique mais trop limité. Le XOR est un opérateur logique qui correspond à un “OU exclusif” : c’est le (A OU B) qu’on utilise en logique mais qui exclue le cas où A et B sont simultanément vrais.
Voici sa table de vérité :
| A | B | A XOR B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
L’une des propriétés les plus importantes et celle-ci :
A XOR B = C
B XOR C = A
Dans notre cas :
cipherText XOR plainText = key
key XOR cipherText = plainText
Exploitation
Après réflexion, je suis parti du principe que le flag commençait par THM{ ce qui fait 4 caractères, et qu’il se termine par }. Sachant cela, j’ai réalisé un programme qui me permettrait d’obtenir la clé et je l’ai utilisé pour obtenir les 2 flags demandés.
Voici le raisonnement :
- En XORant les 4 premiers octets du
cipherTextavecTHM{, on retrouve les 4 premiers caractères de la clé. - En XORant le dernier octet du
cipherTextavec}, on retrouve le 5ème et dernier caractère de la clé.
Une fois la clé complète reconstituée, on XOR l’intégralité du cipherText avec elle pour retrouver le flag 1. On soumet ensuite la clé au serveur pour obtenir le flag 2.
1
2
3
4
5
6
7
8
9
10
11
12
from pwn import xor
encrypted_flag = bytes.fromhex('3e1f043e085b36252b0c2f2f3d040c1e632a2e1b2b393b7619061b302d2d182330750d182f063705')
# Récupération des 4 premiers caractères de la clé via "THM{"
p1_key = xor(encrypted_flag[:4], b"THM{")
# Récupération du 5ème caractère de la clé via "}"
print(xor(encrypted_flag[-1:], b"}"))
# Déchiffrement du flag 1
print(xor(encrypted_flag, "jWIEx")) # jWIEx : est la clé généré aléatoirement par le programme !
Explication technique du programme :
J’ai importé la fonction xor depuis la librairie pwn (également connue sous le nom de pwntools), il s’agit d’une librairie Python spécialisée dans la sécurité informatique et les CTF, elle fournit de nombreux utilitaires pour faciliter l’exploitation de binaires, la communication réseau ou encore la cryptographie.
Suite à cela, j’ai récupéré le flag chiffré et je l’ai converti en bytes à l’aide de bytes.fromhex('...'), ce qui me permet d’appliquer des opérations bit à bit dessus, notamment le XOR.
En exploitant la propriété fondamentale du XOR vue précédemment :
xor(encrypted_flag[:4], b"THM{"): XOR des 4 premiers octets du chiffré avec le début connu du flag, ce qui révèle les 4 premiers caractères de la clé.xor(encrypted_flag[-1:], b"}"): XOR du dernier octet avec le caractère de fermeture}, ce qui révèle le 5ème et dernier caractère de la clé.
Une fois la clé complète reconstituée, il suffit de XORer l’intégralité du texte chiffré avec elle pour retrouver le flag 1 en clair et d’ensuite fournir la clé au service tourant sur le port 1337, afin d’obtenir le flag 2 !
Conclusion
Ce challenge met en évidence deux faiblesses critiques du chiffrement XOR :
L’utilisation d’une clé plus courte que le message à chiffrer, ce qui crée un motif répétitif exploitable. Bonne pratique : la clé ne doit jamais être plus courte que le message à chiffrer.
Connaître une partie du texte en clair suffit à compromettre l’intégralité du chiffrement, c’est ce qu’on appelle une attaque par texte clair connu (known-plaintext attack).

