Description
Every block of three characters in the message was rotated: each plaintext triple [a, b, c] was scrambled to [b, c, a]. Undo the pattern to read the plaintext flag.
Setup
Download message.txt and note that “The flag” appears if you reorder each triple of characters.
Write a short script (Python shown below) to iterate through the text in steps of three and rearrange the characters back to their original order.
wget https://artifacts.picoctf.net/c/191/message.txtpython3 - <<'PY'
flag = []
with open('message.txt') as f:
data = f.read()
for i in range(0, len(data), 3):
block = data[i:i+3]
if len(block) == 3:
flag.append(block[2] + block[0] + block[1])
print(''.join(flag))
PYSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Deduce the permutationObservationI noticed the challenge description stated each plaintext triple [a, b, c] was scrambled to [b, c, a], and that the message begins with readable English, which suggested using the known-plaintext prefix 'The' to confirm and invert the fixed cyclic permutation.Because the first word should read "The", you can infer the mapping. Each plaintext chunk[a b c]was scrambled to[b c a]. To recover the original, read each ciphertext block back in the order positions 2, 0, 1, which gives[a b c].What didn't work first
Tried: Running frequency analysis on the ciphertext to find letter substitutions.
Frequency analysis only works on substitution ciphers where each plaintext letter maps to a fixed ciphertext letter. In a transposition cipher, no letters are changed - only their positions are shuffled - so letter frequencies are identical to normal English and frequency tables give no useful information. The correct approach is positional: figure out which index in each ciphertext triple holds which original letter.
Tried: Trying ROT13 or Caesar shifts on the ciphertext hoping to decode it.
ROT13 and Caesar are substitution ciphers that shift character codes, but the characters in this message are already unchanged from the plaintext. Applying a shift produces garbled output because the problem is ordering, not encoding. Noticing that the first three letters are a recognizable anagram of a common English word like 'The' immediately identifies this as a transposition problem, not a substitution one.
Learn more
A transposition cipher rearranges characters according to a fixed rule without changing what the characters are. In this case, every 3-character plaintext block
[a, b, c]was permuted to[b, c, a](a cyclic left rotation). To recover the original, apply the inverse: from each ciphertext block take position 2, then 0, then 1, which reassembles[a, b, c].Known-plaintext reasoning makes the mapping easy to determine: since English text likely starts with "The", the ciphertext must start with those same letters in scrambled order. Trying different permutations of "T", "h", "e" until the result reads "The" immediately reveals the mapping. This is why knowing any plaintext makes breaking transposition ciphers trivial.
Block transposition was used historically in columnar transposition ciphers, where text was written into a grid and columns were read out in a specified order. The ADFGVX cipher used by Germany in WWI combined a substitution step with columnar transposition - French cryptanalyst Georges Painvin broke it under significant time pressure during the war.
Step 2
Automate the swapObservationI noticed the permutation was consistent across every 3-character block in the file, which suggested a simple Python loop stepping through the ciphertext in increments of 3 and indexing each block as block[2] + block[0] + block[1] would fully reconstruct the plaintext.Loop through the ciphertext in steps of 3, reassemble each block asblock[2] + block[0] + block[1], and concatenate the results. The final sentence ends with picoCTF{...}.What didn't work first
Tried: Using the wrong index order such as block[1] + block[2] + block[0] or block[0] + block[2] + block[1] to reassemble each triple.
There are six possible permutations of three indices, and only one undoes the original scramble. Trying an incorrect order produces readable-looking but wrong text - sometimes still English words by coincidence - which is misleading. The reliable way to find the right order is to test each permutation on the first three characters and check whether the result spells the known plaintext prefix 'The'.
Tried: Looping with range(0, len(data)) and processing every character individually instead of in blocks of 3.
Iterating one character at a time discards the block structure entirely - there is nothing to swap because each block[2], block[0], block[1] reference needs a 3-character slice. The output is either an index error or the ciphertext printed unchanged. The step size in range() must match the cipher's block size, which the challenge description gives as 3.
Learn more
Python's
range(0, len(data), 3)generates indices0, 3, 6, 9, ...- stepping through the string in chunks of 3. Slicingdata[i:i+3]extracts each block. This is the standard Python idiom for processing fixed-width records in a string or byte array.What picoCTF does for non-divisible-by-3 messages. The 2022 challenge ships a message whose length is exactly a multiple of 3, so the edge case never fires in this specific instance. The reference encoder (publicly available with the challenge sources) right-pads with spaces before permuting. That means if you ever see a partial trailing block in a related challenge, the safe options are: (1) drop the trailing remainder if it is whitespace (typical for picoCTF), or (2) preserve the remainder unmodified by appending
data[3 * (len(data) // 3):]to the output. Try option (1) first; fall back to (2) if it mangles the flag.The Python script's
if len(block) == 3check follows option (1): incomplete blocks are skipped. If a future variant pads with a different sentinel character, swap the check forblock.ljust(3)[2] + block.ljust(3)[0] + block.ljust(3)[1]and strip the padding from the final string.In modern cryptography, block ciphers like AES also operate on fixed-size blocks (128 bits for AES), and the handling of messages that don't divide evenly into blocks requires padding schemes (PKCS#7 is most common). Improperly implemented padding led to real vulnerabilities like padding oracle attacks against CBC mode encryption.
Interactive tools
- Rail Fence CipherEncrypt or decrypt rail fence (zigzag) transposition ciphers. Brute-force across rail counts and offsets to find the right setting fast.
Flag
Reveal flag
picoCTF{7R4N5P051N6_15_3XP3N51V3_56E6...}
Any language works; just be careful with the final block if the length isn’t divisible by three.