Contest Source: COMP6[84]41 CTF
Note that all flags have been replaced with “COMP6841{REDACTED}”. This is to discourage you from just blindly submitting the final answer, and to encourage you to follow along and learn something along the way.
We are given two files. First we have spicy_flag.txt
which looks like this:
ሐᤀោᦡಱૹৄ㰐⩀⯤ោ⽄⢤␀㎩ॡ㸄
And another file, called encrypt.py
which looks like this:
from numpy.polynomial import Polynomial
#alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}_1234567890"
def encrypt(plaintext):
= Polynomial([1, 2, 1])
f return "".join(chr(int(f(c))) for c in plaintext)
with open("flag.txt", "rb") as f, open("spicy_flag.txt", "w") as g:
= f.read().strip()
FLAG = encrypt(FLAG)
enc g.write(enc)
Looking at this file, we see that it opens something called flag.txt
, encrypts each character of the flag, and writes it to spicy_flag.txt
. In other words, this is the encryption file that was used to generate the stuff in spicy_flag.txt
, and it’s out job to reverse engineer it in order to get the original flag back out.
One method of solving it is to reverse engineer the mathematics function. Let’s look into the function in more detail. chr(int(f(c))) for c in plaintext
generates a list, with each element corresponding to a single character in the original flag. Looking at numpy documentation, we see that f
is the polynomial defined as \(x^2 + 2x + 1\). Hence, our function is getting the character \(c\), passing it through our polynomial to get \(c^2+2c+1\), and then converting that number into unicode using chr
.
Note that every unicode has a unique identifier known as a code point.
chr
takes a number, and converts into the unicode with that unicode code point. For example,chr(2665)
gives♥
which has code point U+2665.
Now that we understand the function, let’s try to reverse it and find the character that translates into the unicode with code point \(x\). I.e. we’re trying to find a character \(c\) such that \(c^2+2c+1 = x\). Luckily, we know that \(x = c^2+2c+1 = (c+1)^2\), so rearranging in terms of \(x\), we get \(c = \sqrt{x}-1\). Here’s a python script which does this for us. We use ord(x)
in order to obtain the unicode code point of each unicode character.
from isqrt import isqrt
with open("spicy_flag.txt", "r") as f:
= f.readline().strip()
line
= ""
ans for x in line:
print(chr(isqrt(ord(x))-1), end="")
print(ans)
Note, we use the isqrt library, which gives us integer square root. This is nice since we know that all the numbers we are dealing with are perfect squares, so we only need to work with integers
Alternatively if we are bad at maths, there exists another method. As the question title suggests, we can also solve this using brute force. For each unicode character, we can try encrypting every letter in the alphabet until we find one that translates to the same unicode character. They are even nice enough to present the entire alphabet in encrypt.py
. Here’s a python script that does the same thing:
from numpy.polynomial import Polynomial
= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}_1234567890"
alphabet
def encrypt(plaintext):
= Polynomial([1, 2, 1])
f return "".join(chr(int(f(c))) for c in plaintext)
with open("spicy_flag.txt", "r") as f:
= f.readline().strip()
line
= ""
ans for c in line:
for d in alphabet:
if (encrypt(bytes(d, "utf-8") == c):
= ans + d
ans break
print(ans)