4 minutes
FE-CTF 2023 - The Fight of the Unicorn Fans Clubs
The Task at hand
We are given the following task:
Your best friend has asked for your help. She is the president of a
unicorn fan club in a small village in Jutland. For years they have
competed with the neighboring village about who has the best unicorn
fan club. Rumor says that trusted members in the other club have
access to an amazing picture of a unicorn. An insider in the other
fan club has given your friend the following photos. Your friend
hopes that there is something in the photos that can help you gain
access to the other clubs amazing unicorn picture.
And some AI-generated images of hacker-corns, in which we are only really interested in two of them since we can their screens:
Recon
In the first image we can spot the following URL: stackoverflow.com/users/22662719/newbielookingforhelp357
If the check out the URL we get the following:
We can see that there is a github link on the account, lets look there.
URL: github.com/TakingOverTheUnicornWorld
We can see that they have a EncryptorDecryptor repo:
This might have something to do with the base64 on the second image. Looking at the python file we can see that it uses rot4 and fernet to encrypt messages:
# How to encrypt and decrypt my most secret information
# STEP ONE: Caesar Cipher / ROT-n
# since the numerology number of unicorns are 4, I first encode my secret messages with a caesar cipher with a right shift of 4:
import string
alphabets = (string.ascii_lowercase, string.ascii_uppercase, string.digits)
def caesar(text, step, alphabets):
def shift(alphabet):
return alphabet[step:] + alphabet[:step]
shifted_alphabets = tuple(map(shift, alphabets))
joined_alphabets = ''.join(alphabets)
joined_shifted_alphabets = ''.join(shifted_alphabets)
table = str.maketrans(joined_alphabets, joined_shifted_alphabets)
return text.translate(table)
# Write string to encrypt and chose number of shifts
encrypted_caesar=caesar('Here I enter the message I wish to encrypt', step=4, alphabets=alphabets)
# STEP TWO: Fernet encryption
# I then encrypt my output from the caesar encryption with Fernet:
from cryptography.fernet import Fernet
# The following string wil be encrypted
message = encrypted_caesar
#Choose key
key = b'NgsiRrqIgPCK_HS5eyFKAFBUQKHg0iVF4fYCNkiaPtA='
# Instantiate the Fernet class with the key
fernet = Fernet(key)
# Encrypt string
encMessage = fernet.encrypt(message.encode())
print("Final encrypted string: ", encMessage)
#DONE
# To decrypt the Fernet encryption
key = b'NgsiRrqIgPCK_HS5eyFKAFBUQKHg0iVF4fYCNkiaPtA='
fernet = Fernet(key)
decMessage = fernet.decrypt(encMessage).decode()
print("decrypted string: ", decMessage)
“Exploit”
Trying to decrypt the base64 from the second image using the key in the script doesn’t seem to work:
In [1]: from cryptography.fernet import Fernet
In [2]: msg = b'gAAAAABlGGNchBj3H1orqbqt61tAhylu-wAXMzh0ADerA1bRWuX9Tyfpo51sG3dD
...: ja9VK6N3ql6i941F5f4s40YDXKNEPEIDlWysUylZHa8B8OJ8Gw3gHRE='
In [3]: key = b'NgsiRrqIgPCK_HS5eyFKAFBUQKHg0iVF4fYCNkiaPtA='
In [4]: fernet = Fernet(key)
In [5]: fernet.decrypt(msg)
---------------------------------------------------------------------------
InvalidSignature Traceback (most recent call last)
File /opt/homebrew/lib/python3.11/site-packages/cryptography/fernet.py:134, in Fernet._verify_signature(self, data)
133 try:
--> 134 h.verify(data[-32:])
135 except InvalidSignature:
InvalidSignature: Signature did not match digest.
During handling of the above exception, another exception occurred:
InvalidToken Traceback (most recent call last)
Cell In[5], line 1
----> 1 fernet.decrypt(msg)
File /opt/homebrew/lib/python3.11/site-packages/cryptography/fernet.py:91, in Fernet.decrypt(self, token, ttl)
89 else:
90 time_info = (ttl, int(time.time()))
---> 91 return self._decrypt_data(data, timestamp, time_info)
File /opt/homebrew/lib/python3.11/site-packages/cryptography/fernet.py:152, in Fernet._decrypt_data(self, data, timestamp, time_info)
149 if current_time + _MAX_CLOCK_SKEW < timestamp:
150 raise InvalidToken
--> 152 self._verify_signature(data)
154 iv = data[9:25]
155 ciphertext = data[25:-32]
File /opt/homebrew/lib/python3.11/site-packages/cryptography/fernet.py:136, in Fernet._verify_signature(self, data)
134 h.verify(data[-32:])
135 except InvalidSignature:
--> 136 raise InvalidToken
InvalidToken:
The Correct Key
So we need to find another key. This is where I used a lot of time trying to find it, till I stumbled over the users project page, where I found it in a project named private:
So using the new key:
In [6]: key = b'cS-49j8DIDMyBhzt7vwyR_yTqbxhaLCmR1pHLmtC0Aw='
In [7]: fernet = Fernet(key)
In [8]: fernet.decrypt(msg)
Out[8]: b'fmx.pc/yrmgsvr_ai_pszi'
and rot decoding the result we get bit.ly/unicorn_we_love
which leads us to https://www.dropbox.com/scl/fo/kwhyvof1eswx2yhhahqne/h?rlkey=ikxvc7igjntca00r2mxp5da4b
, where we can download a txt file:
But the file is actually a jpeg file:
$ file perfection.txt
perfection.txt: JPEG image data, JFIF standard 1.01, aspect ratio,
density 1x1, segment length 16, baseline, precision 8, 1576x1148,
components 3
$ mv perfection.{txt,jpg}
Flag
And we now got the following image:
And the flag is flag{JuTLaND4ever<3}