PW Crack 2 Beginner picoMini 2022 Solution

Published: April 2, 2026

Description

The password is encoded as chr() calls in the source. Decode it to run the script.

Download level2.py and level2.flag.txt.enc from the challenge page.

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Find the encoded password in the source
    Observation
    I noticed the challenge description says the password is encoded as chr() calls, which indicated the source file contained character-code arithmetic instead of a plain string literal and made inspecting level2.py the obvious first step.
    Open level2.py and locate the password comparison. Instead of a plaintext string, the password is built from chr() calls: chr(0x34) + chr(0x65) + chr(0x63) + chr(0x39).
    Learn more

    Code obfuscation is the practice of making source code harder to read and understand without changing what it does. Using chr() calls instead of literal characters is a very basic form of obfuscation - the password is technically "hidden" from a casual glance, but anyone who knows Python can reconstruct it instantly by evaluating the expression.

    This technique is sometimes used by malware authors or by developers who mistakenly believe it provides security. It does not. Obfuscation only raises the bar slightly; it does not prevent a determined analyst from recovering the secret. The terms security through obscurity describe the (incorrect) belief that hiding implementation details is a substitute for real cryptographic security.

    Reading hex values in source code is a skill worth developing. Common ASCII hex values you will encounter frequently:

    • 0x41-0x5A - uppercase A through Z
    • 0x61-0x7A - lowercase a through z
    • 0x30-0x39 - digits 0 through 9
    • 0x7B / 0x7D - curly braces {} (appear in every picoCTF flag!)
  2. Step 2
    Decode the chr() expression
    Observation
    I noticed the password comparison in level2.py used the expression chr(0x34)+chr(0x65)+chr(0x63)+chr(0x39), which is valid Python syntax, suggesting I could evaluate it directly with python3 -c to instantly recover the plaintext string.
    Evaluate the expression in Python to recover the plaintext password.
    python
    python3 -c "print(chr(0x34)+chr(0x65)+chr(0x63)+chr(0x39))"
    bash
    # Output: 4ec9

    Expected output

    4ec9
    What didn't work first

    Tried: Manually look up each hex value in an ASCII table and piece together the characters by hand without running Python.

    This works in principle but is error-prone for longer expressions and skips the faster approach. Running the exact expression with python3 -c lets Python do the arithmetic and chr() conversion in one step, eliminating transcription errors and scaling to any length expression.

    Tried: Try treating the hex values as a raw byte string using bytes.fromhex() instead of evaluating chr() calls.

    bytes.fromhex() expects a flat hex string like '34ec39', not individual 0x-prefixed integers joined by +. The chr() expression is a Python concatenation of character objects, not a hex blob - you must evaluate it as Python code, not decode it as a hex buffer.

    Learn more

    Python's chr() function converts an integer to a Unicode character. Its inverse, ord(), converts a character back to its integer code point. Together they form the bridge between numeric representations (used in memory and encodings) and human-readable text.

    Using python3 -c to evaluate arbitrary expressions is a quick and powerful technique for decoding obfuscated values during CTF challenges. You can paste the exact expression from the source code and wrap it in print() to immediately see the decoded result - no file creation or script editing required.

    More advanced obfuscation techniques include base64 encoding, XOR encoding, and multi-layer encoding chains. Each layer adds complexity but can still be peeled back with the right tools or by running the decoding logic itself.

  3. Step 3
    Run the script with the decoded password
    Observation
    I noticed that evaluating the chr() expression produced the string '4ec9', which is the value the script expects at its password prompt, so entering it into level2.py would satisfy the comparison and print the flag.
    Execute level2.py and enter 4ec9 when prompted. The flag is printed.
    python
    python3 level2.py
    bash
    # Enter password: 4ec9
    What didn't work first

    Tried: Enter the raw hex bytes 0x34 0x65 0x63 0x39 or the string '0x34+0x65+0x63+0x39' as the password at the prompt.

    The script compares the input against the evaluated Python expression chr(0x34)+chr(0x65)+chr(0x63)+chr(0x39), which resolves to the four ASCII characters '4ec9'. Entering the literal hex notation fails because the comparison is against a decoded string, not its numeric or source-code representation.

    Tried: Try running the script without decoding first, guessing common passwords like 'password', 'admin', or '1234'.

    The password is deterministic and fixed in the source as a specific chr() expression - brute-forcing by hand or with a short wordlist will miss it unless '4ec9' happens to be in the list. Reading the source and evaluating the expression directly is always faster than guessing when source code is available.

    Learn more

    This challenge illustrates an important point about static analysis - examining code without running it. By reading the source and mentally (or programmatically) evaluating the chr() expression, you recovered the password without ever interacting with the script's password prompt. Static analysis is often faster and safer than running unknown code.

    In real security assessments, static analysis is used to audit code for vulnerabilities, recover hardcoded secrets, and understand malware behavior without executing potentially dangerous code. Tools like strings, Ghidra, and IDA Pro extend this capability to compiled binaries where the source code is not available.

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.
  • Hash IdentifierIdentify unknown hash types by length and prefix. Covers MD5, SHA-1, SHA-256, SHA-512, bcrypt, NTLM, and more.

Flag

Reveal flag

picoCTF{tr45h_51ng1ng_...}

chr() obfuscation is a trivial encoding - evaluating the expression in Python immediately reveals the literal string, providing no real protection.

Key takeaway

Security through obscurity relies on an attacker not understanding the hiding mechanism rather than on any mathematical hardness, and it fails the moment the mechanism is recognized. Replacing character literals with chr() calls, encoding strings in base64, or XOR-scrambling constants are all reversible transformations that any analyst can undo by simply running the decoding logic. Real secrecy requires cryptographic keys that are kept separate from the code, not encoding tricks embedded inside it.

Related reading

Want more Beginner picoMini 2022 writeups?

Useful tools for General Skills

What to try next