Whole Lotta Candy
3 minutos de lectura
Se nos proporciona el código fuente en Python del servidor:
from encrypt import Encryptor
from secret import FLAG
import socketserver
import random
import signal
import json
MODES = ['ECB', 'CBC', 'CFB', 'OFB', 'CTR']
class Handler(socketserver.BaseRequestHandler):
def handle(self):
signal.alarm(0)
main(self.request)
class ReusableTCPServer(socketserver.ForkingMixIn, socketserver.TCPServer):
pass
def sendMessage(s, msg):
s.send(msg.encode())
def receiveMessage(s, msg):
sendMessage(s, msg)
return s.recv(4096).decode().strip()
def main(s):
mode = random.choice(MODES)
enc = Encryptor()
while True:
try:
sendMessage(s,
f"Please interact with the server using json data!\n")
sendMessage(s, f"Selected mode is {mode}.\n")
payload = receiveMessage(
s,
"\nOptions:\n\n1.Encrypt flag\n2.Encrypt plaintext\n3.Change mode\n4.Exit\n\n> "
)
payload = json.loads(payload)
option = payload["option"]
if option == "1":
ciphertext = enc.encrypt(FLAG, mode).hex()
response = json.dumps({
"response": "encrypted",
"ciphertext": ciphertext
})
sendMessage(s, "\n" + response + "\n")
elif option == "2":
payload = receiveMessage(s, "Enter plaintext: \n")
payload = json.loads(payload)
plaintext = payload['plaintext'].encode()
ciphertext = enc.encrypt(plaintext, mode).hex()
response = json.dumps({
"response": "encrypted",
"ciphertext": ciphertext
})
sendMessage(s, "\n" + response + "\n")
elif option == "3":
response = json.dumps({"modes": MODES})
sendMessage(
s, "These are the supported modes\n" + response + "\n")
payload = receiveMessage(s, "Expecting modes: \n")
payload = json.loads(payload)
mode = random.choice(payload['modes'])
elif option == "4":
sendMessage(s, "Bye bye\n")
exit()
except Exception as e:
response = json.dumps({"response": "error", "message": str(e)})
sendMessage(s, "\n" + response + "\n")
exit()
if __name__ == "__main__":
socketserver.TCPServer.allow_reuse_address = True
server = ReusableTCPServer(("0.0.0.0", 1337), Handler)
server.serve_forever()
Básicamente, tenemos estas opciones:
$ nc 134.122.106.203 32569
Please interact with the server using json data!
Selected mode is CBC.
Options:
1.Encrypt flag
2.Encrypt plaintext
3.Change mode
4.Exit
>
Modos AES
Tenemos la posibilidad de seleccionar el modo de operación de AES. Existen los siguientes:
- AES ECB
- AES CBC
- AES CFA
- AES OFB
- AES CTR
Cada uno de ellos tiene su propia configuración. Sin embargo, el último es un cifrador en flujo:
Entonces AES solamente se utiliza para generar el flujo de bits que se operará con XOR contra los bits del mensaje.
Ataque de texto claro conocido
Como podemos obtener la flag cifrada con AES CTR y también podemos enviar textos arbitrarios para cifrarlos con el mismo cifrador AES CTR, podemos realizar un ataque de texto claro conocido.
Tenemos lo siguiente:
$$ \mathrm{enc\\_flag} = \mathrm{flag} \oplus \mathrm{key} $$
Entonces, podemos enviar un texto arbitrario $\mathrm{pt}$ y obtener
$$ \mathrm{enc\\_pt} = \mathrm{pt} \oplus \mathrm{key} $$
Debido a las propiedades de la operación XOR, podemos hallar $\mathrm{key}$ (que es el flujo de bits generado mediante AES CTR):
$$ \mathrm{key} = \mathrm{pt} \oplus \mathrm{enc\\_pt} $$
Y por tanto
$$ \mathrm{flag} = \mathrm{enc\\_{flag}} \oplus \mathrm{key} = \mathrm{enc\\_flag} \oplus \mathrm{pt} \oplus \mathrm{enc\\_pt} $$
Realización del ataque
El servidor elegirá un modo de AES de forma aleatorioa, podemos conectarnos varias veces hasta conseguir AES CTR (también se puede cambiar de forma manual):
$ nc 134.122.106.203 32569
Please interact with the server using json data!
Selected mode is CTR.
Options:
1.Encrypt flag
2.Encrypt plaintext
3.Change mode
4.Exit
>
La manera de interactuar con el servidor es en formato JSON:
> {"option":"1"}
{"response": "encrypted", "ciphertext": "ea3796cdc335cce6d497440aede26313cd8726e16c74e069874d952314f60b1a31579ea2dbe9e09101ac8208437139148ffe4512e6f92f46135011849886e300"}
Please interact with the server using json data!
Selected mode is CTR.
Options:
1.Encrypt flag
2.Encrypt plaintext
3.Change mode
4.Exit
>
Ahora usaremos un texto claro conocido de la misma longitud que $\mathrm{enc\_flag}$:
$ python3 -q
>>> len('ea3796cdc335cce6d497440aede26313cd8726e16c74e069874d952314f60b1a31579ea2dbe9e09101ac8208437139148ffe4512e6f92f46135011849886e300')
128
>>> 'A' * (128 // 2)
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
Y ponemos el texto claro:
> {"option":"2"}
Enter plaintext:
{"plaintext":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}
{"response": "encrypted", "ciphertext": "e32295f7b744d2caa1b87c14c1934661b99901901f6acb5df33b8b560adc246b07788093ab9c90be77debb7e5d044f62fadc6f0c97d6314617427d868d95df4010a8f9a051e1cd0b8b224062f9ab70b7"}
Please interact with the server using json data!
Selected mode is CTR.
Options:
1.Encrypt flag
2.Encrypt plaintext
3.Change mode
4.Exit
>
Flag
En este punto, podemos emplear xor
de pwntools
para realizar las operaciones anteriores y conseguir la flag:
>>> from pwn import xor
>>> xor(
... bytes.fromhex('ea3796cdc335cce6d497440aede26313cd8726e16c74e069874d952314f60b1a31579ea2dbe9e09101ac8208437139148ffe4512e6f92f46135011849886e300'),
... b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
... bytes.fromhex('e32295f7b744d2caa1b87c14c1934661b99901901f6acb5df33b8b560adc246b07788093ab9c90be77debb7e5d044f62fadc6f0c97d6314617427d868d95df4010a8f9a051e1cd0b8b224062f9ab70b7')
... )
b'HTB{50_m4ny_m0d35_f02_ju57_4_kn0wn_p141n73x7_4774ck_0n_AES-CTR}\x01\xbb\xde.,\xd3\x95@\xac\x1e\xf4E)U\x08R\xe5'