Skip to main content

crackme-py picoCTF 2021 Solution

Read and analyze a Python script to understand its hidden decoding logic and extract the flag.

Published: April 2, 2026Updated: August 13, 2026

Description

Download the Python script crackme.py and find how to get the flag from the bezos_cc_secret variable.

Download crackme.py.

bash
wget <url>/crackme.py

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Read the source and identify the decode function
    Observation
    crackme.py holds both the encoded flag, in bezos_cc_secret, and a decode_secret function at the top of the file. The password check is only a gate, and the decryption logic can be called directly without satisfying it.
    Open crackme.py. It contains two relevant names: bezos_cc_secret (the encoded flag) and decode_secret (the function that reverses the encoding). The decode function is exposed and callable without solving the password check.
    bash
    less crackme.py
    What didn't work first

    Tried: Run the script normally and try to guess or brute-force the password to get the flag.

    The script prompts for a password and exits on a wrong answer, so brute-forcing through the prompt is slow and pointless. The decode function and the ciphertext both sit above the password logic, so call the decryption directly and never satisfy the check at all.

    Tried: Search the file with strings or grep for a readable flag pattern like picoCTF.

    The flag is stored ROT47-encoded in bezos_cc_secret, so grep and strings output the scrambled ciphertext, not the plaintext. ROT47 rotates every printable ASCII character, making the output look like printable garbage with no recognizable picoCTF prefix until it is decoded.

    Learn more

    What ROT47 actually does. ROT47 rotates each printable ASCII character (code points 33 to 126, i.e. ! through ~) by 47 positions, wrapping inside that 94-character range. So A (65) becomes p (65 + 47 = 112), p becomes A, and so on. Because 47 is exactly half of 94, applying ROT47 twice returns the original string, which makes it self-inverse - the same function encodes and decodes. The decode_secret function implements this shift on the encoded string stored in bezos_cc_secret.

    The reverse-engineering insight: recognize when a program already contains its own decryption routine. Rather than reimplementing the algorithm, you call the existing function with the right input. Common pattern in crackme challenges and in real malware analysis.

  2. Step 2Call decode_secret directly on the hardcoded value
    Observation
    bezos_cc_secret and decode_secret are both defined at module top level, ahead of any password logic. So exec() the script into a Python one-liner's namespace and call decode_secret(bezos_cc_secret) directly, skipping the gate.
    Use exec() to load the script into the current Python session, which defines all its functions and variables. Then call decode_secret(bezos_cc_secret) directly to decode the flag without needing to know the password.
    python
    python3 -c "exec(open('crackme.py').read()); print(decode_secret(bezos_cc_secret))"
    What didn't work first

    Tried: Import crackme as a module with 'import crackme' to access decode_secret and bezos_cc_secret.

    A bare import runs the module at top level, so the password prompt fires before you can touch any names. The exec() one-liner runs top-level code too, but piping /dev/null to stdin, or stubbing out input, suppresses the prompt. A plain import gives you no easier way round it short of editing the file.

    Tried: Implement ROT47 manually in a separate script rather than reusing the decode_secret function from the file.

    A hand-rolled ROT47 works in principle but invites an off-by-one in the character range, 33 to 126 inclusive for 94 characters, or the wrong modulo wrap. Reusing decode_secret removes that risk entirely, because it is the same function the program uses itself, so every implementation detail is correct by construction.

    Learn more

    exec(open('crackme.py').read()) evaluates the entire script as Python code in the current namespace, defining decode_secret and bezos_cc_secret. Note: exec() runs at module top level, which means any top-level code in the script executes, including the password prompt if it's not gated behind if __name__ == '__main__'. If the prompt fires anyway, swap to importlib.util.spec_from_file_location with spec.loader.exec_module(mod) after stripping the prompt block - or simpler, redirect stdin to /dev/null and read the variables from the module's namespace.

    This technique works because the encoding key is implicit in the decode function itself; the function does not require a separate secret key argument. Any encoding scheme that embeds its own decryption logic alongside the ciphertext provides no real security; the attacker just needs to locate and call that logic. For more on Python scripting idioms used across CTF challenges, see Python for CTF.

Interactive tools
  • Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
  • Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.

Flag

Reveal flag

picoCTF{1|/|_4_p34||ut_...}

Per-instance flag with consistent prefix picoCTF{1|/|_4_p34||ut_} and varying 8-hex-char suffix per team (e.g. 4593da8a, dd2c4616, 8c551048 seen across instances).

Key takeaway

Crackmes usually bundle the ciphertext and the decryption routine in the same file, gating access behind a password check rather than a missing key. All the security sits in that gate, none of it in cryptographic strength, so ignoring the gate and calling the decryption function directly is always the first thing to try. The same shape appears in commercial license checks, DRM routines, and malware droppers, wherever the decode logic has to be present at runtime.

Related reading

Useful tools for Reverse Engineering

Where to go next