patchme.py picoCTF 2022 Solution

Published: July 20, 2023

Description

A Python script decrypts flag.txt.enc after checking a password. The password is plainly stored inside the code-use it to reveal the flag.

Open patchme.flag.py (or just grep for the assignment) and note the password hard-coded inside the function (e.g., ak98-=90adfjhgj321sleuth9000).

Run python3 patchme.flag.py, enter the password, and the script prints the flag.

bash
grep -E 'password\s*=' patchme.flag.py
python
python3 patchme.flag.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 function
    Observation
    I noticed patchme.flag.py was provided as plain Python source, which suggested that the password used in the decryption check could be read directly from the code without patching or bypassing anything.
    f stores the password in clear text. Nothing needs to be patched; just use that string when prompted.
    What didn't work first

    Tried: Trying to bypass the password check by editing the script to skip the comparison entirely.

    The challenge title says 'PatchMe', so many beginners assume they need to modify the source. Patching works, but it is unnecessary - the password is stored in plain text in the function, so you can just read it out and use it directly without touching the code.

    Tried: Searching for the password in flag.txt.enc instead of the Python file.

    Beginners sometimes open the .enc file hoping to spot the password near the encrypted data. The .enc file contains only ciphertext and is not human-readable. The password lives in patchme.flag.py, not in the encrypted output.

    Learn more

    Hardcoded secrets are credentials, passwords, or keys embedded directly in source code. This is a critically common vulnerability - developers sometimes store secrets in code for convenience during development, then accidentally ship those files.

    The challenge title "PatchMe" implies you would need to modify the script to bypass the check, but reading the source reveals there's nothing to patch at all. The password is stored in plain sight inside a Python function. Tools like grep, strings, or even a simple text editor are all you need.

    In real security assessments, hunting for hardcoded credentials in source code is a standard step. Tools like truffleHog, gitleaks, and semgrep automate this across entire repositories and git history. Secrets committed to git remain retrievable even after deletion via git log.

    The reason hardcoded secrets persist is partly cultural: during development it's faster to put a password directly in the code than set up a proper secrets management system. Once hardcoded, the secret often gets checked into version control and then propagates - into build artifacts, container images, deployed binaries, and third-party audit logs. By the time someone realizes it's there, rotating the secret requires tracking down every location it was copied to.

    Pre-commit hooks are the most effective automated prevention. Tools like detect-secrets scan each diff before it is committed and block any commit containing high-entropy strings or known secret patterns (AWS access key prefixes, private key headers, connection strings). Integrating this check into CI/CD pipelines as well ensures that secrets don't slip through even if the pre-commit hook is bypassed locally.

  2. Step 2
    Get the flag
    Observation
    I noticed the script accepts a password at runtime and then decrypts flag.txt.enc using it, which meant running the script with the hardcoded password in hand would produce the plaintext flag directly.
    Run the script alongside flag.txt.enc, enter the password, and copy the picoCTF output.
    What didn't work first

    Tried: Running patchme.flag.py from a different directory without flag.txt.enc present.

    The script tries to open flag.txt.enc relative to the current working directory. If only the Python file is in the folder, the script throws a FileNotFoundError. Both files must be in the same directory before running the script.

    Tried: Entering the password with extra whitespace or quotes copied from the source code.

    When copying the password string from the assignment in Python, beginners sometimes include the surrounding quote characters or a trailing newline. The input prompt expects the raw string value - no quotes, no extra spaces - exactly as it appears between the quote characters in the source.

    Learn more

    The script uses the password to decrypt flag.txt.enc - a file that has been encrypted, likely with a symmetric cipher like AES. This is a legitimate decryption workflow; the flaw is that both the key and the ciphertext are distributed together.

    This pattern mirrors real-world mistakes where developers encrypt sensitive files but bundle the decryption key alongside them - in the same repository, Docker image, or binary. Encryption only provides security when the key is kept separate from the ciphertext.

    The lesson: encryption protects data in transit or at rest, but only if key management is done correctly. Symmetric keys should live in environment variables, secret managers (AWS Secrets Manager, HashiCorp Vault), or hardware security modules - never in code.

    An alternative approach to this challenge - consistent with its "PatchMe" title - is to modify the Python script directly. You could replace the password comparison with a no-op (if True:), patch the condition to always return True, or simply call the decryption function directly with any argument. This patching technique mirrors real-world software cracking and license bypass scenarios.

    If this were a compiled binary instead of a Python script, you'd patch at the instruction level: locate the conditional jump (JZ, JNZ) guarding the password check and flip it to its complement, or replace it with a NOP (opcode 0x90) to make the check unconditional. Tools like radare2 and Binary Ninja support interactive binary patching. None of that is needed here because the source is right in front of you.

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{p47ch1ng_l1f3_h4ck_f01e...}

This is a reminder to never ship secrets in client-side code-anyone can read them.

Key takeaway

Encryption protects data only when the key is kept separate from the ciphertext; bundling the decryption password in the same script, image, or repository defeats the entire scheme. Equally, any check that runs on code the user controls can be bypassed by reading or patching that code, whether that means editing a Python comparison or flipping a conditional jump in a compiled binary. Secrets belong in environment variables, secret managers, or hardware modules, never shipped alongside the data they protect.

Related reading

Want more picoCTF 2022 writeups?

Useful tools for Reverse Engineering

What to try next