Short summary:
- Here’s the challenge file: BabyEncryption.zip, zip password is
hackthebox
- Best of luck writing your own decrypting script
Overview (or tl;dr)
The challenge involved reversing a Python script that encrypt a string and write it into a text file. Provided with the encrypted file and the encrypting script, we can reverse the encryption using a bit mathematic and pure brute-forcing.
First look
Filter the “Very Easy” section of Hack The Box challenge, and you will find this at the top of the crypto challenge list. Looking into the given files, we see 2 of them:
aki@kali ~/Downloads $ unzip BabyEncryption.zip
Archive: BabyEncryption.zip
[BabyEncryption.zip] chall.py password:
inflating: chall.py
inflating: msg.enc
Here’s what you will find inside:
# chall.py
import string
from secret import MSG
def encryption(msg):
ct = []
for char in msg:
ct.append((123 * char + 18) % 256)
return bytes(ct)
ct = encryption(MSG)
f = open('./msg.enc','w')
f.write(ct.hex())
f.close()
# msg.enc
6e0a9372ec49a3f6930ed8723f9df6f6720ed8d89dc4937222ec7214d89d1e0e352ce0aa6ec82bf622227bb70e7fb7352249b7d893c493d8539dec8fb7935d490e7f9d22ec89b7a322ec8fd80e7f8921
Now, let’s take a closer look at the file chall.py
.
ct = encryption(MSG)
f = open('./msg.enc','w')
f.write(ct.hex())
f.close()
This snippet takes a message, encrypts it using the encryption()
function, then writes it as hexadecimal to file msg.enc
. Therefore, to solve the challenge, we need to reverse whatever the encryption()
function did to the original message.
And that’s where comes the fun part.
Reverse the encryption
At the heart of the matters, lies this function:
def encryption(msg):
ct = []
for char in msg:
ct.append((123 * char + 18) % 256)
return bytes(ct)
Simple and straight-forward, isn’t it? Line by line, we can see this snippet applies a linear transformation to each character’s ASCII value in the given message:
ct.append((123 * char + 18) % 256)
And if you are new to the mathematical operator “%”, then it’s a modulo operation, a fancy jargon for the remainder of a division. For instance, 5 % 3
(or $5 \mod 3$) is $2$.
If we call the original characters in the message string old
, and new characters made from that new
, then we have this mathematical expression:
$$new=(123 \cdot old + 18)\mod 256$$
Here comes a little arithmetic wizardry. Given any $k \in N$, we can rewrite the equation as follows: $$\Leftrightarrow 123 \cdot old + 18 = new + 256k$$ $$\Leftrightarrow 123 \cdot old = new + 256k - 18$$ $$\Leftrightarrow old = \frac{new + 256k - 18}{123}$$
Now, considering that all ASCII values are integers, both old and new must also be integers. Therefore, our mission is to find a suitable value for $k$ that satisfies this condition.
While mathematicians might invoke intricate number theory to elegantly solve this problem, let’s just be real here: we’re going to brute force it.
Scripting
Basically, this is the grand plan of our mighty script:
With the plan in place, let’s jump into action. First, to read the encrypted message (in hex), we’ll use the following code snippet:
with open("./msg.enc", 'r') as f: # open the file
ct = bytes.fromhex(f.read()) # then read it
print(decrypt(ct)) # and print its decrypted message
And now, the star of the show - the decrypt()
function.
def decrypt(ct):
message = [] # make a storage for all decryped characters
k_value = find_k_value(ct) # and brute force for all k values
for i in range(len(k_value)): # decrypt all characters one-by-one
message.append((256 * k_value[i] + ct[i] - 18) / 123) # by applying the reverse math equation to it
return "".join([chr(int(char)) for char in message]) # and then put them together to read
And then we need to complete the puzzle with the find_k_value()
function:
def find_k_value(ct):
"""Brute force to find the lowest acceptable number k"""
k_value = []
for char in ct:
k = 0
while ((256 * k + char - 18) / 123) % 1 != 0:
k += 1
k_value.append(k)
return k_value
In a nutshell, our final script looks like this:
#!/usr/bin/env python3
def find_k_value(ct):
"""Brute force to find the lowest acceptable number k"""
k_value = []
for char in ct:
k = 0
while ((256 * k + char - 18) / 123) % 1 != 0:
k += 1
k_value.append(k)
return k_value
def decrypt(ct):
message = []
k_value = find_k_value(ct)
for i in range(len(k_value)):
message.append((256 * k_value[i] + ct[i] - 18) / 123)
return "".join([chr(int(char)) for char in message])
def main():
with open("./msg.enc", 'r') as f:
ct = bytes.fromhex(f.read())
print(decrypt(ct))
if __name__ == "__main__":
main()
Now, it’s time to run our masterpiece:
aki@kali ~/Downloads $ python3 sol.py
Th3 nucl34r w1ll 4rr1v3 0n fr1d4y.
HTB{l00k_47_y0u_r3v3rs1ng_3qu4710n5_c0ngr475}
Voila! The flag unfolds before our very eyes, nestled in the second line. A delightful challenge indeed!