jellyCTF
Dizzy_fisherman [899 pts]
Writeup author: lolmenow
Provided files: dizzy_fishman.zip and nc chals.jellyc.tf 4000
Description: Sakana is sending some suspicious looking messages to Dizzy - looks like they’re exchanging a shared secret key to encrypt the messages. Alice has hacked into their key exchange system but needs more help with the exploit. Can you find a way to reveal their secret key and decrypt the message?
Lets take a look at the source before using netcat on the server
import time
from Crypto.Util import number
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
P_LENGTH = 256
MAX_INT = 10000
p = number.getPrime(P_LENGTH)
secret_A = number.getRandomRange(2, MAX_INT)
secret_B = number.getRandomRange(2, MAX_INT)
print("Intercepting communications...")
time.sleep(1)
print("Randomly selected prime p = ", p)
g = int(input("Inject a generator g: "))
if not (1 < g < p):
print("ALERT: Invalid g detected, requires 1 < g < p. Communications terminated!")
exit()
public_key_A = pow(g, secret_A, p)
print("Dizzy's public key (integer) : ", public_key_A)
time.sleep(1)
public_key_B = pow(g, secret_B, p)
print("Sakana's public key (integer): ", public_key_B)
time.sleep(1)
print("Dizzy and Sakana are calculating their secret keys...", end='\n')
secret_dizzy = pow(public_key_B, secret_A, p)
secret_sakana = pow(public_key_A, secret_B, p)
# The secret key should be the same for both parties
assert(secret_dizzy == secret_sakana)
print("Encrypting flag with AES-256 using shared secret key")
with open('flag.txt', 'r') as f:
flag = f.read().strip("\n").encode()
secret = secret_dizzy
encoded_key = secret.to_bytes(32, byteorder='big')
cipher = AES.new(encoded_key, AES.MODE_ECB)
ciphertext = cipher.encrypt(pad(flag, 32))
print("Encrypted flag received: ", ciphertext.hex())
Seems like standard AES encryption but we can’t write the secret as 1
. Well, we need to find a generator g
that, when raised to any power a
, will result in a small number of possible values modulo p
. This would limit the possible values for the public and shared secret keys, making it feasible to brute force the shared secret key.
If g=1
, then (g^a mod p) = 1
for any a
, and the public keys and shared secret key will always be 1. But we can’t make g
1, or can we?
We can actually choose our own generator, g
. So, in order to make g
one, we can just simply do g=p-1
So, the public keys will always be either 1
or p - 1
, and the shared secret key will also always be either 1
or p - 1
Here is how we can do this:
adam@DESKTOP-J07EICU:~$ nc chals.jellyc.tf 4000
Intercepting communications...
Randomly selected prime p = 86899728650592841884861211249399967893443125213865378821666077709714002156393
Inject a generator g: 86899728650592841884861211249399967893443125213865378821666077709714002156392
Dizzy's public key (integer) : 86899728650592841884861211249399967893443125213865378821666077709714002156392
Sakana's public key (integer): 1
Dizzy and Sakana are calculating their secret keys...
Encrypting flag with AES-256 using shared secret key
Encrypted flag received: 2c6783bc372fbf601a4159080bf295e439c30e16fecde63dc7066abb40825383b1d8b2267d641fc17fd54d8bb0a60203b1d8b2267d641fc17fd54d8bb0a60203
Now that the secret shared key is 1
, we can simply decrypt this with a python script!
Here is the script I made:
sol.py
# mandatory libraries
from Crypto.Util.Padding import unpad
from Crypto.Cipher import AES
secret = 1 # we know the secret is 1 because g = p-1
encoded_key = secret.to_bytes(32, byteorder='big') # convert first to bytes
cipher = AES.new(encoded_key, AES.MODE_ECB) # create a new AES cipher object with the shared secret key
encrypted_flag = bytes.fromhex('2c6783bc372fbf601a4159080bf295e439c30e16fecde63dc7066abb40825383b1d8b2267d641fc17fd54d8bb0a60203b1d8b2267d641fc17fd54d8bb0a60203') # encrypted flag
flag = unpad(cipher.decrypt(encrypted_flag), 32) # decrypt
print(flag.decode())
# some code was inspired from this blog: https://onboardbase.com/blog/aes-encryption-decryption/
Running the script, we get: jellyCTF{SOS_stuck_in_warehouse}
And that is our flag!
Final flag: jellyCTF{SOS_stuck_in_warehouse}