📂 Download encoder.
📂 Download ciphertext.
Description: This is the Custom Cyclical Cipher! Enclose the flag in our wrapper for submission. If the flag was “example” you would submit “picoCTF{example}”.
Difficulty: Medium
Author: Matt Superdock
Summary
This challenge involves C3 (Custom Cyclical Cipher), a custom encryption algorithm that uses differential encoding with lookup tables. We’re given the encoder program and ciphertext, but need to reverse-engineer the decryption process. By understanding how the cipher works, we can write a simple reversal script to recover the original flag.
Analysis
Challenge Files
We have two components:
- convert.py - The encoder/encryption program that shows us the algorithm
- ciphertext - The encrypted message we need to decrypt
How the Encryption Works
Here’s the complete encoder program:
import syschars = ""from fileinput import inputfor line in input(): chars += line
lookup1 = "\n \"#()*+/1:=[]abcdefghijklmnopqrstuvwxyz"lookup2 = "ABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrst"
out = ""
prev = 0for char in chars: cur = lookup1.index(char) out += lookup2[(cur - prev) % 40] prev = cur
sys.stdout.write(out)Breaking down the algorithm:
-
Reading Input:
from fileinput import inputfor line in input():chars += linefileinput.input()reads from files passed as command-line arguments (or stdin)- This loops through each line in the input file
- All lines are concatenated into the
charsvariable - So if the plaintext file has multiple lines, they’re all combined into one string
-
The Lookup Tables:
lookup1=\n "#()*+/1:=[]abcdefghijklmnopqrstuvwxyz(41 characters - valid plaintext alphabet)lookup2=ABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrst(40 characters - valid ciphertext alphabet)
-
Differential Encoding Process:
- For each character in the input plaintext:
- Find its position in
lookup1(call thiscur) - Calculate the difference from the previous position:
(cur - prev) % 40 - Use this difference as an index into
lookup2to get the encrypted character - Save the current position as
prevfor the next iteration
- Find its position in
- For each character in the input plaintext:
-
The “Cyclical” Part:
- The modulo 40 operation wraps differences around, creating a cyclic pattern
- This is why it’s called the “Custom Cyclical Cipher”
Example Walkthrough
Let’s say we’re encrypting “ab”:
-
First character ‘a’:
- Index in lookup1: 33
- prev = 0
- difference = (33 - 0) % 40 = 33
- lookup2[33] = ‘h’
- Output: ‘h’, prev = 33
-
Second character ‘b’:
- Index in lookup1: 34
- prev = 33
- difference = (34 - 33) % 40 = 1
- lookup2[1] = ‘B’
- Output: ‘B’, prev = 34
-
Final ciphertext: “hB”
The Encrypted Message
DLSeGAGDgBNJDQJDCFSFnRBIDjgHoDFCFtHDgJpiHtGDmMAQFnRBJKkBAsTMrsPSDDnEFCFtIbEDtDCIbFCFtHTJDKerFldbFObFCFtLBFkBAAAPFnRBJGEkerFlcPgKkImHnIlATJDKbTbFOkdNnsgbnJRMFnRBNAFkBAAAbrcbTKAkOgFpOgFpOpkBAAAAAAAiClFGIPFnRBaKliCgClFGtIBAAAAAAAOgGEkImHnIlThis is what we need to decrypt.
Solution
Reversing the Encryption
Since encryption uses only addition and modulo, it’s easily reversible:
Encryption: difference = (cur - prev) % 40 → lookup2[difference]
Decryption: We reverse this:
- Find the encrypted character in
lookup2to get the difference - Add the difference to prev: cur = (difference + prev) % 40
- Look up the character in
lookup1 - Update prev for the next character
Decryption Script
lookup1 = "\n \"#()*+/1:=[]abcdefghijklmnopqrstuvwxyz"lookup2 = "ABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrst"
ciphertext = "DLSeGAGDgBNJDQJDCFSFnRBIDjgHoDFCFtHDgJpiHtGDmMAQFnRBJKkBAsTMrsPSDDnEFCFtIbEDtDCIbFCFtHTJDKerFldbFObFCFtLBFkBAAAPFnRBJGEkerFlcPgKkImHnIlATJDKbTbFOkdNnsgbnJRMFnRBNAFkBAAAbrcbTKAkOgFpOgFpOpkBAAAAAAAiClFGIPFnRBaKliCgClFGtIBAAAAAAAOgGEkImHnIl"
plaintext = ""prev = 0
for char in ciphertext: # Step 1: Find the difference from lookup2 diff = lookup2.index(char)
# Step 2: Calculate the original index cur = (diff + prev) % 40
# Step 3: Get the plaintext character plaintext += lookup1[cur]
# Step 4: Update prev for next iteration prev = cur
print(plaintext)Running the Script
$ python decrypt.py ciphertext#asciiorder#fortychars#selfinput#pythontwo
chars = ""from fileinput import inputfor line in input(): chars += line
for i in range(len(chars)): if i == b * b * b: print chars[i] #prints b += 1 / 1Understanding the Decrypted Output
The decrypted text is actually a Python script! Looking at it carefully:
- Comments:
#asciiorder,#fortychars,#selfinput,#pythontwo - A loop that prints characters at specific positions: where
i == b * b * b
This means it prints at positions: 1³=1, 2³=8, 3³=27, 4³=64, 5³=125, 6³=216… (perfect cubes!)
Fixing the Script
The decrypted script had some issues (using / instead of proper integer, old Python 2 syntax):
#asciiorder#fortychars#selfinput#pythontwo
chars = ""from fileinput import inputfor line in input(): chars += line
b = 1
for i in range(len(chars)): if i == b * b * b: print(chars[i], end='') # Fixed: Python 3 syntax b += 1Changes made:
- Changed
b = 1 / 1tob = 1(proper integer) - Changed
print chars[i]toprint(chars[i], end='')(Python 3 syntax) - Changed
b += 1 / 1tob += 1(proper increment)
Extracting the Flag
Running the fixed script extracts characters at cube positions:
- Position 1 (1³): ‘a’
- Position 8 (2³): ‘d’
- Position 27 (3³): ‘l’
- Position 64 (4³): ‘i’
- Position 125 (5³): ‘b’
- Position 216 (6³): ‘s’
The hidden flag: adlibs
$ cat decrypted.txt | python solve.pyadlibsâš¡ Raikiri🎉 Flag pwned!
Final answer: picoCTF{adlibs}
💡 TL;DR / Lesson Learned✅ Differential Encoding - Encrypting based on differences between consecutive positions
✅ Lookup Tables - Using predefined character mappings for substitution
✅ Modular Arithmetic - The% 40creates the cyclic behavior
✅ Stateful Cipher - The cipher maintains state (prev) affecting each character
✅ Reversible Operations - Addition and modulo are easily reversed for decryption
✅ Layered Puzzles - The plaintext itself contains another puzzle (cube position extraction)