Description
This is the Custom Cyclical Cipher! Download the ciphertext here. Download the encoder here. Enclose the flag in our wrapper for submission. If the flag was "example" you would submit "picoCTF{example}".
Setup
Download ciphertext and convert.py to the same directory.
Run the provided script locally with Python 3.
wget https://artifacts.picoctf.net/c_titan/47/ciphertext && \
wget https://artifacts.picoctf.net/c_titan/47/convert.pySolution
Walk me through it- Step 1Implement the inverseTranslate convert.py into a decryptor: swap lookup1/lookup2 roles, replace (cur - prev) with (cur + prev), and keep prev synced with the decrypted index.python
python3 decrypt.py ciphertext > decrypted.txtLearn more
The C3 cipher is a cyclical differential cipher: each character's encoding depends on the previously emitted character. The two lookup tables in
convert.pyare strings (or lists) that map between plaintext and ciphertext alphabets.lookup1.index(ch)returns the position of a plaintext character;lookup2[cur]returns the ciphertext character at that position.# encryption (paraphrased) prev = 0 for ch in plaintext: idx = lookup1.index(ch) # plain -> index cur = (idx - prev) % len(lookup2) # subtract previous out.append(lookup2[cur]) # index -> cipher prev = cur # chain forward (ciphertext index)The
prevchain variable holds the most recent ciphertext index, not the plaintext index, because the cipher feeds back its own output. To invert, walk the ciphertext left-to-right, swap the table roles, and turn subtraction into addition:# decryption prev = 0 for ch in ciphertext: cur = lookup2.index(ch) # cipher -> index idx = (cur + prev) % len(lookup2) # undo the subtraction out.append(lookup1[idx]) # index -> plain prev = cur # same chain variable as encryptionModular arithmetic is what makes inversion possible:
(x - prev) mod mhas a unique inverse(y + prev) mod mbecause addition mod m is a bijection.The provided script is Python 2. When porting to Python 3 watch for:
printas a function,range()returning a generator, integer division spelled//(regular/now produces floats), andstrversusbyteswhen reading the ciphertext file.In real cryptography this self-feeding construction is formalized in CFB (Cipher Feedback) mode with AES. The toy weakness here: if you know any plaintext-ciphertext pair (or even just the prefix
picoCTF), you can verify the lookup tables and decrypt the whole stream. - Step 2Feed the decrypted Python program to itselfAfter decryption, the output is a Python 2 program that reads from stdin and samples at cubic indices. Pipe the decrypted Python source back into itself as its own input: cat decrypted.py | python decrypted.py (or pipe convert.py through the decryptor and then into itself). The extracted characters spell out the flag body which you wrap in picoCTF{...}.bash
cat convert.py | python3 decrypt.py | python3 -The output is the flag body (e.g.,
adlibs). Wrap it aspicoCTF{adlibs}for submission.Learn more
The decrypted ciphertext is not raw flag text: it is another Python program that samples characters at cubic positions from its own standard input. Piping the decrypted program back into itself provides that input, extracting the hidden phrase embedded at positions 1, 8, 27, 64, 125 (n3).
Embedding a secret by sampling at cubic indices is a steganographic technique. The message is hidden within a larger body of text, with only specific positions carrying meaningful data. The key lesson is to read the decrypted output carefully rather than assuming it is immediately the flag. The challenge says "self input," meaning the program expects its own source as input.
In Python 3, iterating with
enumerate()and checking ifiis a perfect cube (by computinground(i**(1/3))**3 == i) is the idiomatic approach. The provided script may be Python 2; port it or run with Python 2 if available.
Related guides
Python for CTF
Porting the provided Python 2 script to Python 3 is half the work. The Python guide covers the print/range/division changes plus the bytes vs str split that trips up file readers.
Base64, Hex, and Common CTF Encodings Explained
This challenge uses a custom substitution cipher. The encodings guide covers classical ciphers, Caesar/ROT variants, and the identification techniques that help you recognize what you are dealing with.
Flag
picoCTF{adl...}
The cubic sampling script prints the final flag body.
How to prevent this
How to prevent this
Custom encoding schemes give the illusion of secrecy. They are not encryption, just obfuscation.
- If you need confidentiality, use AEAD (AES-GCM, XChaCha20-Poly1305). If you only need to encode binary data for transport, use Base64 + a clearly labeled MAC. Do not mix the two.
- Assume your encoding will be reverse-engineered. Anything client-side or in shipped code is recoverable; security must rely on a secret key, not a secret algorithm (Kerckhoffs' principle).
- For sensitive data in URLs, cookies, or QR codes, use a signed token (JWT with HS256/EdDSA, or PASETO) backed by a server-side secret. Plain encoding gives zero security guarantees.