Traces
14 minutos de lectura
Se nos proporciona el código fuente en Python de un servidor:
from db import *
from Crypto.Util import Counter
from Crypto.Cipher import AES
import os
from time import sleep
from datetime import datetime
def err(msg):
print('\033[91m'+msg+'\033[0m')
def bold(msg):
print('\033[1m'+msg+'\033[0m')
def ok(msg):
print('\033[94m'+msg+'\033[0m')
def warn(msg):
print('\033[93m'+msg+'\033[0m')
def menu():
print()
bold('*'*99)
bold(f"* 🏰 Welcome to EldoriaNet v0.1! 🏰 *")
bold(f"* A mystical gateway built upon the foundations of the original IRC protocol 📜 *")
bold(f"* Every message is sealed with arcane wards and protected by powerful encryption 🔐 *")
bold('*'*99)
print()
class MiniIRCServer:
def __init__(self, host, port):
self.host = host
self.port = port
self.key = os.urandom(32)
def display_help(self):
print()
print('AVAILABLE COMMANDS:\n')
bold('- HELP')
print('\tDisplay this help menu.')
bold('- JOIN #<channel> <key>')
print('\tConnect to channel #<channel> with the optional key <key>.')
bold('- LIST')
print('\tDisplay a list of all the channels in this server.')
bold('- NAMES #<channel>')
print('\tDisplay a list of all the members of the channel #<channel>.')
bold('- QUIT')
print('\tDisconnect from the current server.')
def output_message(self, msg):
enc_body = self.encrypt(msg.encode()).hex()
print(enc_body, flush=True)
sleep(0.001)
def encrypt(self, msg):
encrypted_message = AES.new(self.key, AES.MODE_CTR, counter=Counter.new(128)).encrypt(msg)
return encrypted_message
def decrypt(self, ct):
return self.encrypt(ct)
def list_channels(self):
bold(f'\n{"*"*10} LIST OF AVAILABLE CHANNELS {"*"*10}\n')
for i, channel in enumerate(CHANNELS.keys()):
ok(f'{i+1}. #{channel}')
bold('\n'+'*'*48)
def list_channel_members(self, args):
channel = args[1] if len(args) == 2 else None
if channel not in CHANNEL_NAMES:
err(f':{self.host} 403 guest {channel} :No such channel')
return
is_private = CHANNELS[channel[1:]]['requires_key']
if is_private:
err(f':{self.host} 401 guest {channel} :Unauthorized! This is a private channel.')
return
bold(f'\n{"*"*10} LIST OF MEMBERS IN {channel} {"*"*10}\n')
members = CHANNEL_NAMES[channel]
for i, nickname in enumerate(members):
print(f'{i+1}. {nickname}')
bold('\n'+'*'*48)
def join_channel(self, args):
channel = args[1] if len(args) > 1 else None
if channel not in CHANNEL_NAMES:
err(f':{self.host} 403 guest {channel} :No such channel')
return
key = args[2] if len(args) > 2 else None
channel = channel[1:]
requires_key = CHANNELS[channel]['requires_key']
channel_key = CHANNELS[channel]['key']
if (not key and requires_key) or (channel_key and key != channel_key):
err(f':{self.host} 475 guest {channel} :Cannot join channel (+k) - bad key')
return
for message in MESSAGES[channel]:
timestamp = message['timestamp']
sender = message['sender']
print(f'{timestamp} <{sender}> : ', end='')
self.output_message(message['body'])
while True:
warn('You must set your channel nickname in your first message at any channel. Format: "!nick <nickname>"')
inp = input('guest > ').split()
if inp[0] == '!nick' and inp[1]:
break
channel_nickname = inp[1]
while True:
timestamp = datetime.now().strftime('%H:%M')
msg = input(f'{timestamp} <{channel_nickname}> : ')
if msg == '!leave':
break
def process_input(self, inp):
args = inp.split()
cmd = args[0].upper() if args else None
if cmd == 'JOIN':
self.join_channel(args)
elif cmd == 'LIST':
self.list_channels()
elif cmd == 'NAMES':
self.list_channel_members(args)
elif cmd == 'HELP':
self.display_help()
elif cmd == 'QUIT':
ok('[!] Thanks for using MiniIRC.')
return True
else:
err('[-] Unknown command.')
server = MiniIRCServer('irc.hackthebox.eu', 31337)
exit_ = False
while not exit_:
menu()
inp = input('> ')
exit_ = server.process_input(inp)
if exit_:
break
Análisis del código fuente
Básicamente, el servidor implementa una especie de servidor IRC para permitir a los usuarios enviar y recibir mensajes en diferentes canales. Cada uno de los canales tiene una clave para cifrar cada mensaje del canal:
def __init__(self, host, port):
self.host = host
self.port = port
self.key = os.urandom(32)
El método de cifrado es AES en modo CTR utilizando la clave anterior:
def encrypt(self, msg):
encrypted_message = AES.new(self.key, AES.MODE_CTR, counter=Counter.new(128)).encrypt(msg)
return encrypted_message
def decrypt(self, ct):
return self.encrypt(ct)
Obsérvese que el contador se genera en cada llamada a la función, lo que provocará una situación de reutilización del nonce.
El propósito de usar el cifrado es ocultar mensajes anteriores en un canal. Entonces, si nos unimos a un canal en este momento, el historial de los mensajes estará cifrado:
def output_message(self, msg):
enc_body = self.encrypt(msg.encode()).hex()
print(enc_body, flush=True)
sleep(0.001)
Sin embargo, todavía podemos recopilar información sobre nombres de usuario y marcas de tiempo:
for message in MESSAGES[channel]:
timestamp = message['timestamp']
sender = message['sender']
print(f'{timestamp} <{sender}> : ', end='')
self.output_message(message['body'])
Por ejemplo, debemos enviar comandos especiales para unirnos un canal o dejar un canal:
while True:
warn('You must set your channel nickname in your first message at any channel. Format: "!nick <nickname>"')
inp = input('guest > ').split()
if inp[0] == '!nick' and inp[1]:
break
channel_nickname = inp[1]
while True:
timestamp = datetime.now().strftime('%H:%M')
msg = input(f'{timestamp} <{channel_nickname}> : ')
if msg == '!leave':
break
Solución
Entonces, conectémonos a este servidor IRC y veamos lo que tenemos aquí:
$ nc 83.136.252.13 57111
***************************************************************************************************
* 🏰 Welcome to EldoriaNet v0.1! 🏰 *
* A mystical gateway built upon the foundations of the original IRC protocol 📜 *
* Every message is sealed with arcane wards and protected by powerful encryption 🔐 *
***************************************************************************************************
> HELP
AVAILABLE COMMANDS:
- HELP
Display this help menu.
- JOIN #<channel> <key>
Connect to channel #<channel> with the optional key <key>.
- LIST
Display a list of all the channels in this server.
- NAMES #<channel>
Display a list of all the members of the channel #<channel>.
- QUIT
Disconnect from the current server.
***************************************************************************************************
* 🏰 Welcome to EldoriaNet v0.1! 🏰 *
* A mystical gateway built upon the foundations of the original IRC protocol 📜 *
* Every message is sealed with arcane wards and protected by powerful encryption 🔐 *
***************************************************************************************************
> LIST
********** LIST OF AVAILABLE CHANNELS **********
1. #general
2. #secret
************************************************
Genial, tenemos dos canales: #general
y #secret
. Sin embargo, #secret
requiere una clave de acceso:
> JOIN #secret
:irc.hackthebox.eu 475 guest secret :Cannot join channel (+k) - bad key
Entonces, debemos comenzar con #general
:
> JOIN #general
[23:30] <Doomfang> : 6a61814696a35f034eecb2ca1095
[23:32] <Stormbane> : 6a61814696a348184ef3b9c91f9c2c
[23:34] <Runeblight> : 6a61814696a349194fe4b6c71795210e
[00:00] <Stormbane> : 1c6acf5398a37c0355a1b58b10973e5a0f445d559c30fb8e5fc20239f8a84bb168c5feb8a951527ce5723e3d94bcc72d85ed27ad4e4976f5d359243774c411f6209f
[00:01] <Doomfang> : 1e618c408ff06f034ee5fa8b36933a5a0f4548079872f69e4e8c5630f3f119a763c7fcebe8173e6df930203d8dbcc52fcaf722b6004d3ee7ce1737203ddd05f065dd943f926fed7f586238
[00:02] <Runeblight> : 05609c0584e66f4001e3a1df5ebb6e175b4e45109e39fd954cc2053ef0ed19a164d5e1bee61d3e6af832237c93aa8c7dece472ab064f67b4d41c3a36789205f169918229c622e977467327c3edf0838c4f260bae02e9a044d9733ac5a1ea49221ac32e716154
[00:04] <Doomfang> : 1f678156dde0730d4fefb1c75e9b3a5a154259558e33f29e0b841923bde456ba6d80e6aaeb1a6d37b1192869d8aa822ed2eb26bc060a6afb871621373dc202eb33d08129c63def7f4329
[00:04] <Doomfang> : 036a9a40ddea684c55e9b18b0e933a090b455f148e37b49d4490563ee8fa19a76fc3e7b9e2517d71f03b237893e38278c8eb60b8186256d7f14c321a76d112bf1f858319aa3eef49642168f9
[00:05] <Runeblight> : 0c609c0594f7354c6eefb8d25e81211b09480d1c8972e3925f8a563ee8fa19b965d3e6ebf3036b6ae530293d9eb5ce34c0f17c
[00:06] <Stormbane> : 126a9b0bddcc6e1e01edb5d80ad224150d480d189c2bb4934a941371f1ed5fa02ad4e0aae4146d37b102283d92acd12985e037ff184f6ced871a353778d405ee6b
[00:07] <Doomfang> : 022885059eeb7e0f4ae8bacc5e9d3c085b4142128e72e0940b801371eefd4bb12acefdebf3037f7af475227bdfb6d72f85e331ab074570e7870b31287cdb1ef16b
[00:08] <Runeblight> : 006a8d55ddee7e4c54f1b0ca0a972d545b644b55893af1820b811725fee019bb648cb2bce2567275b13d2c6b9af9d63285e331ab4e4c7fe7d357
[00:09] <Stormbane> : 02288449dde0740151e0a6ce5e86211f5b414c019821e0db4f830230bdff50a06280fdbef5517c78f23e386ddfa9ce3ccbac72880b0a73e1d40d74206fd303e765d09b35c63ce977402770ceacf183de5e6943a818ede0
[00:10] <Doomfang> : 0269c8408be6691555e9bdc519d220095b4e41109c20b8db5c87563cf2fe5cf47ecfb2bfef143e77f42d393d8cadc33ac0ac72901b583ef3c818386574c150f52cc59d25886ff2754f646f85
[00:11] <Runeblight> : 03608441ddec754201c8f3c65e812c1f12434a558e26e69a45851371eee15eba6bcce1ebe1037174b13a38698cb0c6388ba205ba4e4777f3cf0d7427789207e331d29d298261
[00:12] <Stormbane> : 1c6ac8469ced3c1801f5b5c01bd22814020d5f1c8e39e7d50bae1325bafb19b86fc1e4aea7057670e2752e759eb7cc38c9a230ba08456cf1870d3c20649204f024d29e6c933cae
[00:13] <Doomfang> : 0a689a4098e7354c6ceea2ce5e9325165b594c199621b48f44c20239f8a849a663d6f3bfe2516c76fe38633dadaccc38c7ee3bb8065e32b4d71531246ed750e129d4943ec63be8750e6b68ccffa68ec9492c05
[00:14] <Runeblight> : 1e618c408ff06f034ee5fa8b37d5245a1f445e16923cfa9e48961f3ffaa857bb7d8eb282e1516a71f42c6d759eafc77dd6e737b14e5f6db8870e316570c703f665d59c3f873ff0754f7527c2e1eb83c852285fa806f1e0
[00:53] <Doomfang> : 6a638d448be6
[00:53] <Stormbane> : 6a638d448be6
[00:53] <Runeblight> : 6a638d448be6
You must set your channel nickname in your first message at any channel. Format: "!nick <nickname>"
Muy bien, tenemos muchos mensajes encriptados, pero algunos de ellos parecen similares…
[23:30] <Doomfang> : 6a61814696a35f034eecb2ca1095
[23:32] <Stormbane> : 6a61814696a348184ef3b9c91f9c2c
[23:34] <Runeblight> : 6a61814696a349194fe4b6c71795210e
...
[00:53] <Doomfang> : 6a638d448be6
[00:53] <Stormbane> : 6a638d448be6
[00:53] <Runeblight> : 6a638d448be6
Estos mensajes corresponden a los primeros y últimos mensajes. Por lo tanto, deben corresponder a !nick <username>
y !leave
.
Cifrado en flujo
Recordemos que el servidor está utilizando AES en modo CTR y usando el mismo contador en cada mensaje cifrado. Como resultado, el método de cifrado se convierte en un cifrado XOR con un key stream repetido.
Solo como referencia, AES en modo CTR funciona como cifrado de flujo, donde el contador se cifra usando bloques AES, pero el cifrado real ocurre cuando se aplica XOR entre este resultado (key stream) y el texto claro:
Los cifrados en fllujo son vulnerables a ataques de texto claro conocido, por lo que si sabemos parte del texto claro, podemos usar XOR para encontrar parte del key stream. Este problema ocurre cuando se reutiliza el key stream, que es exactamente el escenario presentado en este reto.
Por ejemplo, sabemos que el tercer mensaje corresponde a !nick Runeblight
, por lo que podemos obtener parte del key stream:
$ python3 -q
>>> from pwn import unhex, xor
>>>
>>> key_stream = xor(unhex('6a61814696a349194fe4b6c71795210e'), b'!nick Runeblight')
>>> key_stream
b'K\x0f\xe8%\xfd\x83\x1bl!\x81\xd4\xab~\xf2Iz'
Con este key stream, ahora podemos descifrar otros mensajes:
>>> xor(key_stream, unhex('6a61814696a35f034eecb2ca1095'), cut='min')
b'!nick Doomfang'
>>> xor(key_stream, unhex('6a61814696a348184ef3b9c91f9c2c'), cut='min')
b'!nick Stormbane'
>>> xor(key_stream, unhex('6a638d448be6'), cut='min')
b'!leave'
Entonces, hemos confirmado que el ataque de texto claro conocido funciona y también que los mensajes corresponden a los comandos !nick
y !leave
.
Ahora, podemos descifrar mensajes parcialmente, solo hasta la longitud del key stream. Por ejemplo:
>>> xor(keystream, unhex('1c6acf5398a37c0355a1b58b10973e5a0f445d559c30fb8e5fc20239f8a84bb168c5feb8a951527ce5723e3d94bcc72d85ed27ad4e4976f5d359243774c411f6209f'), cut='min')
b"We've got a new "
>>> xor(keystream, unhex('1e618c408ff06f034ee5fa8b36933a5a0f4548079872f69e4e8c5630f3f119a763c7fcebe8173e6df930203d8dbcc52fcaf722b6004d3ee7ce1737203ddd05f065dd943f926fed7f586238'), cut='min')
b'Understood. Has '
>>> xor(keystream, unhex('05609c0584e66f4001e3a1df5ebb6e175b4e45109e39fd954cc2053ef0ed19a164d5e1bee61d3e6af832237c93aa8c7dece472ab064f67b4d41c3a36789205f169918229c622e977467327c3edf0838c4f260bae02e9a044d9733ac5a1e\
a49221ac32e716154'), cut='min')
b"Not yet, but I'm"
>>> xor(keystream, unhex('1f678156dde0730d4fefb1c75e9b3a5a154259558e33f29e0b841923bde456ba6d80e6aaeb1a6d37b1192869d8aa822ed2eb26bc060a6afb871621373dc202eb33d08129c63def7f4329'), cut='min')
b'This channel is '
Crib dragging
Por lo tanto, solo nos queda tratar de adivinar algunos caracteres y palabras para encontrar el key stream completo. Esta técnica se llama crib dragging. Para este propósito, podemos usar el siguiente script en Python:
#!/usr/bin/env python3
from pwn import sys, unhex, xor
filename = sys.argv[1]
with open(filename) as f:
encs = [unhex(line.split('> : ')[1]) for line in f if '> : ' in line]
key = unhex(sys.argv[2])
if len(sys.argv) >= 5:
index = int(sys.argv[3])
known = sys.argv[4].encode()
if len(sys.argv) == 6 and sys.argv[5] == 'back':
key_stream = key_stream[:-len(known)]
else:
key_stream += xor(encs[index][len(key_stream):], known, cut='min')
print(key_stream.hex())
for i, enc in enumerate(encs):
print(i, xor(key_stream, enc, cut='min'), file=sys.stderr)
Está programado de tal forma que los mensajes descifrados se impriman a stderr
y el nuevo key stream se imprima a stdout
. De esta manera, podemos usar variables de entorno para actualizar el valor del key stream. Luego, debemos decirle el texto claro deducido y el índice del mensaje.
Por ejemplo, comenzamos con un key stream vacío y sabemos que el índice 2
es !nick Runeblight
:
$ KEY_STREAM=''
$ KEY_STREAM=$(python3 solve.py general.txt "$KEY_STREAM" 2 '!nick Runeblight')
0 b'!nick Doomfang'
1 b'!nick Stormbane'
2 b'!nick Runeblight'
3 b"We've got a new "
4 b'Understood. Has '
5 b"Not yet, but I'm"
6 b'This channel is '
7 b'Here is the pass'
8 b'Got it. Only sha'
9 b'Yes. Our last mo'
10 b"I'm checking our"
11 b'Keep me updated.'
12 b"I'll compare the"
13 b'If everything is'
14 b"Hold on. I'm see"
15 b"We can't take an"
16 b'Agreed. Move all'
17 b"Understood. I'm "
18 b'!leave'
19 b'!leave'
20 b'!leave'
$ echo $KEY_STREAM
a57bec5c7283b9c7c3f090c117bd981e
Ahora podemos comenzar a deducir. Por ejemplo, el mensaje 14
podría continuar con 'ing '
:
$ KEY_STREAM=$(python3 solve.py general.txt "$KEY_STREAM" 14 'ing ')
0 b'!nick Doomfang'
1 b'!nick Stormbane'
2 b'!nick Runeblight'
3 b"We've got a new tip "
4 b'Understood. Has ther'
5 b"Not yet, but I'm che"
6 b'This channel is not '
7 b'Here is the passphra'
8 b'Got it. Only share i'
9 b'Yes. Our last move m'
10 b"I'm checking our log"
11 b'Keep me updated. If '
12 b"I'll compare the lat"
13 b'If everything is cle'
14 b"Hold on. I'm seeing "
15 b"We can't take any ri"
16 b'Agreed. Move all tal'
17 b"Understood. I'm disc"
18 b'!leave'
19 b'!leave'
20 b'!leave'
En caso de que tengamos un error al adivinar, podemos volver atrás agregando un parámetro back
:
$ KEY_STREAM=$(python3 solve.py general.txt "$KEY_STREAM" 14 'ing ')
0 b'!nick Doomfang'
1 b'!nick Stormbane'
2 b'!nick Runeblight'
3 b"We've got a new tip i|'i5"
4 b'Understood. Has therm>*y$'
5 b"Not yet, but I'm cheku!r&"
6 b'This channel is not {\x7f.ya'
7 b'Here is the passphra{{hz.'
8 b'Got it. Only share i|>?u5'
9 b'Yes. Our last move might '
10 b"I'm checking our log{><sa"
11 b'Keep me updated. If |v-ea'
12 b"I'll compare the latmm<<%"
13 b'If everything is cleild<6'
14 b"Hold on. I'm seeing {j:}/"
15 b"We can't take any ri{u;2a"
16 b'Agreed. Move all talcmhh.'
17 b'Understood. I\'m discgp&y"'
18 b'!leave'
19 b'!leave'
20 b'!leave'
$ KEY_STREAM=$(python3 solve.py general.txt "$KEY_STREAM" 14 'ing ' back)
0 b'!nick Doomfang'
1 b'!nick Stormbane'
2 b'!nick Runeblight'
3 b"We've got a new tip "
4 b'Understood. Has ther'
5 b"Not yet, but I'm che"
6 b'This channel is not '
7 b'Here is the passphra'
8 b'Got it. Only share i'
9 b'Yes. Our last move m'
10 b"I'm checking our log"
11 b'Keep me updated. If '
12 b"I'll compare the lat"
13 b'If everything is cle'
14 b"Hold on. I'm seeing "
15 b"We can't take any ri"
16 b'Agreed. Move all tal'
17 b"Understood. I'm disc"
18 b'!leave'
19 b'!leave'
20 b'!leave'
Podemos notar que una deducción es incorrecta cada vez que veamos caracteres raros y palabras que no tengan sentido.
Entonces… después de muchas suposiciones mediante prueba y error, terminamos con estos mensajes:
!nick Doomfang
!nick Stormbane
!nick Runeblight
We've got a new tip about the rebels. Let's keep our chat private.
Understood. Has there been any sign of them regrouping since our last move?
Not yet, but I'm checking some unusual signals. If they sense us, we might have to change[...]
This channel is not safe for long talks. Let's switch to our private room.
Here is the passphrase for our secure channel: %mi2gvHHCV5f_kcb=Z4vULqoYJ&oR
Got it. Only share it with our most trusted allies.
Yes. Our last move may have left traces. We must be very careful.
I'm checking our logs to be sure no trace of our actions remains.
Keep me updated. If they catch on, we'll have to act fast.
I'll compare the latest data with our backup plan. We must erase any sign we were here.
If everything is clear, we move to the next stage. Our goal is within reach.
Hold on. I'm seeing strange signals from outside. We might be watched.
We can't take any risks. Let's leave this channel before they track us.
Agreed. Move all talks to the private room. Runeblight, please clear the logs here.
Understood. I'm disconnecting now. If they have seen us, we must disappear immediately.
!leave
!leave
!leave
Con esto, tenemos la clave de acceso para #secret
:
> JOIN #secret %mi2gvHHCV5f_kcb=Z4vULqoYJ&oR
[00:10] <Doomfang> : 380667b22fb275f2f01eb373979d
[00:12] <Stormbane> : 380667b22fb262e9f001b870989476
[00:14] <Runeblight> : 380667b22fb263e8f116b77e909d7b6c
[00:26] <Runeblight> : 4e0d2ea22cfd44f1fb53be779c8a3377c53dbba095a4e7e299c892bdc631ea306a8f743a34f2c6dc64fd45b8453c16ea618139ac0b129969baa511efdb3eff2766c9f8581040e548051ef67aa04873cc6ed5f10a7fe5ca2b110c8286539dfd64d753456d0b84b54e1aec24ea
[01:01] <Doomfang> : 580f7cb421f61fbdcb1bb0329c947675c968e8f08aa6e6f984d5d5fadc3bef7529c0523771a2ccdb63f144ec48330ea832e83efe1a5a927feee603fedb23ad273c8cf7161540e64f034deb7ebc113ccf37dff75828e0db3b104b989d1a9de67cd74445790a89b70012ef21a1d01731b2cbf3d5e0c0dd03ead86bebbca9fb04cc11944a0f7b967f23dde5bb00eca5b98a4f37b468dedc7f15710453ee44f82e7ec1c928ccc3bfe64beaf8c4d259c7983c72d26f
[01:02] <Runeblight> : 504f78b464f054f8f153a6668c9e6a71de28bba491a0a9f882c796f8dd74f43022db003034bac0c774b855e10d320ff432d12abb185b9873bda50be4db2ae3362b9df0591a13bd070b50ff3bbd5e3ecc63d8eb446fa4d82d1c4085ce41cffd7ad51345411697fb4e1af420ab821d76bcc4bdd5e485c41cb7d854efefe4fd12d645da4a0e2fd76026d5fcfa18f7e0a4974f25bf2dd9c134146c534ebe4ff83c3ddac825d7dfbba554a2f6c58017db823c22ce2759fc74ad877d2458af387ef3624036a0acc89efa3c444a1cba7bac51f40f4dd85c77248a0fcd26f5084c36c9b89fe2eb5ebe1bf613f246d0e5a3fc26b55ae21ce52b9480a2d3e9d0aa92914db87fdf60ae7046ed3c43168769b2d6b0283110610d771a2bed04a9334001068e76f0c37b14ea0c460d0f74cbf849499c93bb5ce97b171f48eaf54fd9c13c69f98f085c535c29956604a4093c2b0dd96e5d96294b086f78e1c58e87
[01:03] <Stormbane> : 504f63f125fe43f8fe17ac329a887c6bc362f8b89ca6e2e59ec1d5f2db26b82634ca4c3e26bddbc230f950f9443309f232d530bb4e539965a7e00cfe9839e821259bfd455a40d8414a4af372bd1131cc76d3ed4428f3df3b595c979c429dfd72925c0b2e0c89bf450da032aa931e37bdd6f0c3fa9484508ddf50e2bca2e719dc118a571434913d6ff0e7ef54eda6f0911b64b87e8cce3c0e6a5245be43f22a7295d260dddffef245eaffd0d61c948d7972d32e1be562e0c8281944e02978fc6b4038e29d8096a97f484b0ab37ee056f50f4fde5b6b288a018f61ba0d0d2dd97a7c19f754fa48fb5af24885ffeae82eb15ab14be9388980bcd0fc93ad93914eb27fc370bf2246f7794f0cc26df9d68c212800261c6c41
[01:04] <Doomfang> : 4e0d2eb225fc5ff2eb53b4749f95617c9027fea390b1e8f899c99bb38e1dfe7530c7492171bbda8971b855ea483c19ee3e812cb60b5cd772a6e042c2d12ce5620986ec581709fd00191efd74bc5236da37dde35328e5d23a1c4d929716dff734dd5345611697fb540de13ea8de5613a5c7f386e088cd50b7955de2f0a1fd03985c93560f3a9c766fd1fdee18e0e0b4970029f162d9dd7f1f6d5049ec48bd3e3fd8d669d1d6b0ab009df291cd0cc7987961ce2f1fe075e0c6692410af2474ec274d7ea097d585a97d554608b87aac57f44946d95b7a3ec4008070ff5a0e3ad8f6dceef15faa49fb1bbb5495e9e4
[01:05] <Runeblight> : 5c106fb230fe48b3bf32bb76d99f657dde6ff2b6d9b2ecac82c398fcc73ab8202adc45373ff2cfc662b859f75a715af1778136bb0b56d765a1eb16e3d62ce82c2990b9461801ff54441ed27dee453bcc37f3ed5f66e7d724594a999c42d4f47dd74e45671796fb4d1ee73ea7911a76b1c3efd4fd85da03e8d84bebbca7e102d455da49142892332ed1f1fe07f7e0a4974f30b968c5dd7f0977564ff04af53232d1d52698f5b1a557afb7d9c10fd1cc3822d2241ae669e9877a2910ad2f76e7740477e6d8c599fd6e5e0500b03ff85bf40f53c541722c9611c161fb0e097fd4ebdcfefb53b65ef049
[01:06] <Stormbane> : 400d7dfd64f044e9bf04b032948f606c903be9b598b1a9e584869af3c22db834378f41723db3dadd30ea52eb422f0ea832e83efe1957d767adf10bfcd93fe862239db9421b0fb1540551f537ee46368965d9f14128f6db3e1c4d9a8758dab27dc64e45620c86ba5416ef39ead03f22f3cbee86f881ca15a89d58aefdb7b457f065b85e38299e7110f6e0fa13e3a9be9f3001a97dc0c0360e625049f143c20a37c1ce57f3d4a7da6ea5f9d2c526e6892c71c46004
[01:07] <Doomfang> : 5e0761b56ab27ff2bf01b07196887738df29bbb98de5e4f983d2d5f8d63deb2164c64e7225bacc8967ea5eec593814a666ce35bb1d1cd74feef20be6d46be82c399ceb535401fd4b4a4ae97aad54208976c2e70a6df6df3b1c48dace57d3f634db49457d0b84b74c5fee32b2950476b1c7bdd5e48fc315aad853e8bcabfe12d65d830b5b1291333bdaf7bb11eaa5bd814f21a768de8f331f62564eed0df23b7edcd22498c6bba557a3fbdd8011d59a3c22cf2e59fa62ee89663410a32276e7644136
[01:08] <Runeblight> : 580f7cb421f61fbdcb1bb0329495617d9038fef09dacfaef85d586bdc720b47530c7457236a0ccc864fd45b859351fa660c82bb54012b270abf71baad524e027249db9411140f542065fe237ee453bcc37f3ed5f66e7d724595f829c53d3f560da580b7d438caf535fe432a2951825b6d1b386c385881db18b48aefda7fa57cb5e954b5b39927520c0f7bb1bf1b2f08f062ab562db8f301c234b50ee42ef292bdbcf7cc191bde94fb9f2c28e
[01:09] <Stormbane> : 4e0d2ea22cfd44f1fb53b07c9dda6770d93cbbbd9ca0fde59ec1d5fcc030b8382bd9457225bd89c830f558ea487d09e371d42abb4e419668adf117e7966bc4246a9df1531d12b14a0b59fe68ee5e218964c0eb4f7ba4df3a1c0c958259cefb7ad51d0c604fc5af481af977a9910f76bacce9c3e683cd00b0d853fbeee4f918ca55890b5b0c923322c7e1ef54eaafa4d81b25ba688cdb371b770443f64cf33e3b9b8644ddc5fef148a3e491c21c94983167812d18fa73ad8b6d2343a12d72a96e4a38f490c984a96c4b440ab331
[01:09] <Runeblight> : 38046bb032f7
[01:09] <Stormbane> : 38046bb032f7
[01:09] <Doomfang> : 38046bb032f7
You must set your channel nickname in your first message at any channel. Format: "!nick <nickname>"
Y nuevamente… necesitamos usar la crib dragging para descifrar estos mensajes:
$ KEY_STREAM=''
$ KEY_STREAM=$(python3 solve.py secret.txt "$KEY_STREAM" 2 '!nick Runeblight' back)
0 b'!nick Doomfang'
1 b'!nick Stormbane'
2 b'!nick Runeblight'
3 b'We should keep o'
4 b'Agreed. The enem'
5 b"I've been studyi"
6 b"I'm already cros"
7 b'We cannot afford'
8 b'Exactly. And eve'
9 b'Yes, but we must'
10 b'Good. No record '
11 b'Agreed. The more'
12 b'We should end th'
13 b'!leave'
14 b'!leave'
15 b'!leave'
$ KEY_STREAM=$(python3 solve.py secret.txt "$KEY_STREAM" 5 'ng ' back)
0 b'!nick Doomfang'
1 b'!nick Stormbane'
2 b'!nick Runeblight'
3 b'We should keep our '
4 b"Agreed. The enemy's"
5 b"I've been studying "
6 b"I'm already cross-c"
7 b'We cannot afford he'
8 b'Exactly. And even i'
9 b'Yes, but we must tr'
10 b'Good. No record of '
11 b'Agreed. The more we'
12 b'We should end this '
13 b'!leave'
14 b'!leave'
15 b'!leave'
Flag
Nuevamente, después de muchas suposiciones… terminamos descifrando casi todos los mensajes y encontrando la flag en uno de ellos:
!nick Doomfang
!nick Stormbane
!nick Runeblight
We should keep our planning here. The outer halls are not secure, and too many eyes watch the open channels.
Agreed. The enemy's scouts grow more persistent. If they catch even a whisper of our designs, they will move against us. We must not allow their seers or spies to track our steps.
I've been studying the traces left behind by our previous incantations, and something feels wrong. Our network of spells has sent out signals to an unknown beacon-one that none of us authorized. This could be [...]
I'm already cross-checking our spellwork against the ancient records. If this beacon was part of an older enchantment, I'll find proof. But if it is active now, then we have a problem. It could be a concealed [...]
We cannot afford hesitation. If this is a breach, then the High Council's forces may already be on our trail. Even the smallest mistake could doom our entire campaign. We must confirm at once if our arcane def[...]
Exactly. And even if we remain unseen for now, we need contingency plans. If the Council fortifies its magical barriers, we could lose access to their strongholds. Do we have a secondary means of entry if the [...]
Yes, but we must treat it only as a last resort. If we activate it too soon, we risk revealing its location. It is labeled as: HTB{Crib_Dragging_Exploitation_With_Key_Nonce_Reuse!}
Good. No record of it must exist in the written tomes. I will ensure all traces are erased, and it shall never be spoken of openly. If the enemy ever learns of it, we will have no second chance.
Agreed. The more we discuss it, the greater the risk. Every moment we delay, the Council strengthens its defenses. We must act soon before our window of opportunity closes.
We should end this meeting and move to a more secure sanctum. If their mages or spies are closing in, they may intercept our words. We must not take that chance. Let this be the last message in this place.
!leave
!leave
!leave
Y la flag es:
HTB{Crib_Dragging_Exploitation_With_Key_Nonce_Reuse!}