Whole Lotta Candy
3 minutes to read
We got the Python source code of the server:
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()
Basically, we have these options:
$ 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
>
AES modes
We are able to select AES operation mode. We have the following ones:
- AES ECB
- AES CBC
- AES CFA
- AES OFB
- AES CTR
All of them have their own configuration. However, the last one is actually a stream cipher:
So AES is only used to generate a bit stream that will be operated with XOR against the plaintext bits.
Known plaintext attack
Since we can get the flag encrypted with AES CTR and also send arbitrary plaintext to encrypt them using the same AES CTR cipher, we will be able to perform a known plaintext attack.
We have this:
$$ \mathrm{enc\\_flag} = \mathrm{flag} \oplus \mathrm{key} $$
Then, we can send an arbitrary plaintext $\mathrm{pt}$ and obtain
$$ \mathrm{enc\\_pt} = \mathrm{pt} \oplus \mathrm{key} $$
Because of XOR properties, we can find the $\mathrm{key}$ (which is the bit stream generated by AES CTR):
$$ \mathrm{key} = \mathrm{pt} \oplus \mathrm{enc\\_pt} $$
And therefore
$$ \mathrm{flag} = \mathrm{enc\\_{flag}} \oplus \mathrm{key} = \mathrm{enc\\_flag} \oplus \mathrm{pt} \oplus \mathrm{enc\\_pt} $$
Performing the attack
The server will choose an AES mode randomly, we can try several times until we get AES CTR (we could have also switched manually):
$ 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
>
We need to interact with the server in JSON format:
> {"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
>
Now we will use a known plaintext of the same size as $\mathrm{enc\_flag}$:
$ python3 -q
>>> len('ea3796cdc335cce6d497440aede26313cd8726e16c74e069874d952314f60b1a31579ea2dbe9e09101ac8208437139148ffe4512e6f92f46135011849886e300')
128
>>> 'A' * (128 // 2)
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
Now we enter this plaintext:
> {"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
At this point, we can use xor
from pwntools
to do the above computation and find the 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'