crackme-py picoCTF 2021 Solution

Published: April 2, 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 1
    Read the source and identify the decode function
    Observation
    I noticed crackme.py contains both the encoded flag in bezos_cc_secret and a decode_secret function at the top of the file, which suggested the password check is gating access unnecessarily and the decryption logic could be called directly without ever satisfying the prompt.
    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 unnecessary. The decode function and the ciphertext are both defined at the top of the file before any password logic, so you can call the decryption directly without ever satisfying the password check.

    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 2
    Call decode_secret directly on the hardcoded value
    Observation
    I noticed that bezos_cc_secret and decode_secret are both defined at module top level before any password logic, which suggested using exec() to load the script's namespace into a Python one-liner and calling decode_secret(bezos_cc_secret) directly to bypass the authentication gate entirely.
    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 statement runs the module at top level, which triggers the password prompt immediately before you can access any names. The exec() one-liner also runs top-level code, but piping /dev/null to stdin or appending '; input = lambda *a: ""' suppresses the prompt. A regular import offers no easier way to skip the prompt unless you edit the file.

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

    A manual ROT47 implementation works in principle but risks an off-by-one in the character range (33 to 126 inclusive, 94 characters total) or the wrong modulo wrap. Reusing decode_secret from the file eliminates that risk because it is the exact same function the program itself uses to verify the password, so any implementation detail in the original is automatically correct.

    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 writeups).

Key takeaway

Crackme programs often bundle both the ciphertext and the decryption routine in the same binary or script, gating access behind a password check rather than a missing key. The security is entirely in the authentication gate, not in cryptographic strength, so bypassing or ignoring the gate and calling the decryption function directly is always the first thing to try. The same pattern appears in commercial software license checks, DRM routines, and obfuscated malware droppers where the decode logic must be present at runtime.

Related reading

Want more picoCTF 2021 writeups?

Useful tools for Reverse Engineering

What to try next