Skip to main content

PW Crack 2 Beginner picoMini 2022 Solution

Read a Python script to decode the obfuscated password and unlock the flag.

Published: April 2, 2026Updated: August 13, 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 1Find the encoded password in the source
    Observation
    The description says the password is encoded as chr() calls. So the source holds character-code arithmetic rather than a plain string literal, which makes level2.py the obvious first thing to open.
    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 2Decode the chr() expression
    Observation
    The password comparison in level2.py uses chr(0x34)+chr(0x65)+chr(0x63)+chr(0x39), which is valid Python. Evaluating it directly with python3 -c recovers the plaintext string instantly.
    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 0x-prefixed integers joined with +. The chr() expression is a Python concatenation of characters, not a hex blob, so evaluate it as code rather than decoding it as a 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 3Run the script with the decoded password
    Observation
    Evaluating the chr() expression gives '4ec9', which is exactly what the script expects at its password prompt. Entering it into level2.py satisfies the comparison and prints 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 fixed in the source as a specific chr() expression, so brute-forcing by hand or with a short wordlist misses it unless '4ec9' happens to be in the list. When the source is available, reading it and evaluating the expression always beats guessing.

    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 depends on the attacker not recognizing the hiding mechanism, rather than on anything mathematically hard, and it fails the moment the mechanism is spotted. Swapping character literals for chr() calls, base64-encoding strings, or XOR-scrambling constants are all reversible, and any analyst undoes them by running the decoding logic. Real secrecy needs cryptographic keys kept separate from the code, not encoding tricks embedded in it.

Related reading

Useful tools for General Skills

Where to go next