basic-mod1 picoCTF 2022 Solution

Published: July 20, 2023

Description

A list of integers is provided along with instructions to take each number mod 37 and map it onto a custom alphabet (A-Z, digits, underscore). Implement the mapping to decode the hidden flag.

Grab the message file and convert the space-separated numbers into a Python list.

Apply modulo 37 to each entry, then treat 0-25 as A-Z, 26-35 as digits, and 36 as an underscore.

Concatenate the recovered characters and wrap them with picoCTF{...}.

bash
wget https://artifacts.picoctf.net/c/128/message.txt
bash
cat message.txt
bash
sed -e "s/^/[/" -e 's/ *$//' -e "s/$/]/" -e "s/ /, /g" message.txt
python
python3 mod1.py

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Normalize the input
    Observation
    I noticed message.txt contains a long line of space-separated integers, which suggested parsing it programmatically via sed and Python's split rather than transcribing by hand, to avoid silent transcription errors.
    Wrap the space-separated numbers as a Python list with one sed invocation, then iterate.
    What didn't work first

    Tried: Manually copying the numbers from the file and typing them into a Python script by hand.

    This works for small inputs but is error-prone and slow when the list has dozens of entries. A missed digit or an extra space breaks the decode silently. Using wget plus sed or Python's open/read/split pipeline is faster and eliminates transcription errors.

    Tried: Pasting the raw space-separated string directly as a Python variable without any conversion.

    Python treats a bare string of space-separated digits as a string, not a list of integers. Calling int() on the whole string fails. You need .split() to break it into tokens and int() on each token before doing arithmetic.

    Learn more

    One sed invocation, four -e expressions applied in order to the same stream: -e "s/^/[/" prepends [, -e 's/ *$//' trims trailing spaces, -e "s/$/]/" appends ], and -e "s/ /, /g" rewrites every space as , . The result is a valid Python list literal in one pass.

    You could just open the file in Python and call .split() - the sed trick is a habit, not a requirement. Useful when you want a single shell-pipeline solution.

  2. Step 2
    Map each value
    Observation
    I noticed the problem statement explicitly specifies modulo 37 and an alphabet of exactly 37 characters (A-Z, 0-9, underscore), which indicated that taking each number mod 37 and indexing into that ordered character set would decode every integer to its corresponding letter or digit.
    For every number n, compute n % 37 to keep it inside the alphabet range, then index into the character set ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.
    What didn't work first

    Tried: Mapping the raw numbers directly onto the alphabet without applying mod 37 first.

    The numbers in message.txt are much larger than 36, so direct indexing into a 37-character string raises an IndexError or produces garbage characters. The mod step is what squashes the value into the valid range 0-36 before the lookup.

    Tried: Building the alphabet as '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_' (digits first, then letters).

    The challenge specifies letters first (A=0 through Z=25), then digits (0=26 through 9=35), then underscore (=36). Reversing digits and letters causes every decoded character to be wrong. Always match the order defined in the problem statement.

    Learn more

    Modular arithmetic reduces a number to the remainder after dividing by a modulus. Here the modulus is 37 because the custom alphabet has exactly 37 characters (26 letters + 10 digits + 1 underscore). Any integer, no matter how large, maps to a value in 0..36 after % 37.

    The encoding rule is a simple positional table:

    index 0..25  -> 'A'..'Z'
    index 26..35 -> '0'..'9'
    index 36     -> '_'
    
    Worked example for n = 306:
      306 / 37 = 8.27...     (integer quotient = 8)
      8 * 37   = 296
      306 - 296 = 10         (remainder)
      alphabet[10] = 'K'
    
    More:
      617 % 37 = 25 -> 'Z'    (16 * 37 = 592, 617 - 592 = 25)
       28 % 37 = 28 -> '2'    (digit slot: 26 + 2)

    37 is prime, which means every non-zero value has a multiplicative inverse modulo 37 - a property the follow-up challenge basic-mod2 leans on.

    Python's % follows the divisor's sign: with a positive modulus the result is always in [0, m), even for negative dividends. C does not guarantee this; in C the sign can carry from the dividend, so -1 % 37 may give -1 instead of 36. A single comprehension - [alphabet[n % 37] for n in numbers] - is enough here.

  3. Step 3
    Assemble the flag
    Observation
    I noticed the decoded characters form a plaintext inner string, which suggested joining them and wrapping the result in the standard picoCTF{} format that the platform's checker requires.
    Build a string starting with picoCTF{...} to produce the final answer.
    What didn't work first

    Tried: Submitting just the decoded inner text without the picoCTF{} wrapper.

    The platform's flag checker expects the full picoCTF{...} format. Submitting only the inner characters is rejected even though the decode is correct. Always wrap the result in the standard prefix before submitting.

    Learn more

    Once every integer is decoded into its character, joining the list with ''.join(chars) and wrapping it in the standard picoCTF{} format gives the submission-ready flag. This final assembly step is trivial but teaches an important habit: always verify the structure of your output matches what the challenge expects before submitting.

    The picoCTF{} wrapper is a flag format convention. Most CTF platforms use a consistent prefix (like flag{}, CTF{}, or competition-specific variants) so automated checkers can validate submissions. Recognizing these patterns helps you quickly confirm you decoded something correctly even before you know what the inner text means.

Interactive tools
  • Cipher Identifier & Auto-DecoderPaste any ciphertext and the tool auto-runs every common decoder (base64, hex, Morse, ROT, Atbash, Bacon, binary, decimal, URL) and ranks the results by English-likeness.

Flag

Reveal flag

picoCTF{R0UND_N_R0UND_B6B...}

Simple modular arithmetic plus a custom alphabet turns the numeric sequence back into the plaintext flag.

Key takeaway

Modular arithmetic maps arbitrarily large integers into a fixed range by taking the remainder after division, which is why it underpins nearly every modern cryptographic primitive. Substitution encodings that reduce values modulo an alphabet size are a simple application of the same concept, and recognizing the alphabet length as the modulus is the first step to reversing any such scheme. The same mod-and-index pattern appears in Caesar ciphers, Vigenere ciphers, and the index arithmetic inside AES S-box lookups.

Related reading

Want more picoCTF 2022 writeups?

Tools used in this challenge

What to try next