To start off, we are given a text file containing some encrypted data and the program that was used to encrypt the data. The encryption program is as follows:

import sys import random key = sys.argv[1] flag = '**CENSORED**' assert len(key) == 13 assert max([ord(char) for char in key]) < 128 assert max([ord(char) for char in flag]) < 128 message = flag + "|" + key encrypted = chr(random.randint(0, 128)) for i in range(0, len(message)): encrypted += chr((ord(message[i]) + ord(key[i % len(key)]) + ord(encrypted[i])) % 128) print(encrypted.encode('hex'))

Now we know how it was encrypted and how the plaintext is structured! Let’s take a look at the encrypted data.

enc = 7c 15 3a 47 4b 6a 2d 3f 7d 3f 73 28 70 3e 6c 2d 24 3a 08 3e 2e 77 3c 45 54 77 48 66 7c 15 11 33 3f 4f 74 5e

So what do we already know about this?

- enc[0] is already decrypted
- enc[1:7] is “TWCTF{” this is our flag format
- enc[20:22] is “}|” this is the end of the flag and the separator between the flag and the key
- enc[22:] is the encrypted key

So now we need to figure out how to reverse the encryption algorithm.

encrypted += chr((ord(message[i]) + ord(key[i % len(key)]) + ord(encrypted[i])) % 128)

Because we know the first part of the flag, we can use that to retrieve a partial key! That is done through the magic of math

\(E_{n+1} = \left ( P_{n} + K_{n \mod 13} + E_{n} \right ) \mod 128\)

Because of the nature of the input, we can make an educated guess about the values of the above equation pre-mod.

\(w = \left ( E_n + M_n \right )\mod 128\)

\(g = E_{n+1}\)

\(k_n = \left\{\begin{matrix}128-w+g & w>g\\ g-w & w \leq g\end{matrix}\right.\)

Or if you’d rather read a program,

encrypted = "7C153A474B6A2D3F7D3F7328703E6C2D243A083E2E773C45547748667C1511333F4F745E" hexbytes = [encrypted[i:i+2] for i in range(0,len(encrypted),2)] key = "" message = "TWCTF{" def getadd(work,goal): work = work%128 if work > goal: out = 128-work+goal else: out = goal-work return(out) i = len(message)-1 for i,byte in enumerate(hexbytes): work = int(byte,16) + ord(message[i]) goal = int(hexbytes[i+1],16) print(chr(getadd(work,goal)), end="")

Running through this will give us the first 6 characters of the key, which happen to be “ENJ0YH” at this point, we made an educated guess that the whole key was “ENJ0YHACKING!” and ran a decryption program

encrypted = "7C153A474B6A2D3F7D3F7328703E6C2D243A083E2E773C45547748667C1511333F4F745E" hexbytes = [encrypted[i:i+2] for i in range(0,len(encrypted),2)] message = "" key = "ENJ0YHACKING!" key = [ord(c) for c in key] def getadd(work,goal): work = work%128 if work > goal: out = 128-work+goal else: out = goal-work return(out) for i,byte in enumerate(hexbytes[:-1]): work = key[i%13] + int(hexbytes[i],16) goal = int(hexbytes[i+1],16) message += chr(getadd(work,goal)) print(message)

This didn’t work, not entirely at least. After removing the unprintable characters from the output, we’re left with “TWCTF{Q{wkg-is-fun/z@A\0YHOLIDOb”

It decrypted part of the key at the end! Looks like the key is actually “ENJ0YHOLIDAY!”

Re-running the above program with the new key gives us the output “TWCTF{Crypto-is-fun!}|ENJ0YHOLIDAY!” and our flag **TWCTF{Crypto-is-fun!}**

~T