Skip to main content

PW Crack 4 Beginner picoMini 2022 Solution

Crack a hashed password by testing a set of candidate values. A Python reversing and hash analysis challenge.

Published: April 2, 2026Updated: September 20, 2026

Description

This time there are 100 candidate passwords. One of them hashes to the stored MD5 hash. Add a loop to test all 100.

Download level4.py - it contains a list of 100 candidate passwords and a stored MD5 hash.

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 script structure
    Observation
    The description mentions a list of 100 candidate passwords and a stored MD5 hash. Understanding how level4.py is structured comes first, so you know where the loop logic needs to go.
    Open level4.py. There's a list of 100 candidate passwords and a target hash. The script checks one password at a time - modify it to loop through all of them.
    Learn more

    Scaling from 7 to 100 candidates makes manual testing impractical - at 100 entries, you might still manage it manually, but the pattern clearly demands automation. This is a deliberate pedagogical progression: each pw-crack level increases the candidate pool until manual approaches are clearly infeasible, pushing you toward writing code.

    Understanding the script's structure before modifying it is essential. Locate:

    • Where the candidate list is defined
    • Where the stored hash is defined
    • Where the password is compared to the hash
    • Where the decryption function is called with the correct password

    This structural reading skill - understanding how code flows before changing it - is called code comprehension and is fundamental to both software development and security analysis. Never modify code you do not understand.

  2. Step 2Modify the script to loop through all candidates
    Observation
    The script already holds every variable it needs, pw_list, correct_pw_hash, and user_pw, but only checks one password. Adding a for loop over all 100 candidates, hashing each and setting user_pw on a match, is the whole change.
    Open level4.py in a text editor. Replace the line that reads the password from input() with a for loop over the candidate list (pw_list) that hashes each entry with MD5 and, when the hash matches the stored hash, sets user_pw to that entry and breaks. With the prompt gone the script runs straight through and decrypts on its own.
    Learn more

    The key insight is to modify the existing script rather than writing a separate one. The script already has all the variables needed: pw_list (the 100 candidates), correct_pw_hash (the target), and user_pw / user_pw_hash (what gets passed to the decryption function). Note that pw_list is defined near the bottom of the file, after the check function is called, so put the loop somewhere the list already exists: either move pw_list above the function or run the loop after it and pass the winner in.

    The loop to add looks like:

    for pw in pw_list:
        if hash_pw(pw) == correct_pw_hash:
            user_pw = pw
            user_pw_hash = hash_pw(pw)
            break

    Because the loop supplies user_pw directly, the interactive prompt goes away entirely. The script runs start to finish with no typing, the comparison succeeds on the matching candidate, and the decryption prints the flag.

    In real-world password auditing, this same loop logic is the core of tools like John the Ripper and hashcat. The difference is that those tools add GPU acceleration, rule-based mutations, and support for hundreds of hash algorithms. The fundamental algorithm is unchanged.

  3. Step 3Run the modified script
    Observation
    Once the loop pre-fills user_pw with the matching candidate, the XOR decryption in level4.py runs on its own. So executing the modified script produces the flag.
    With the correct password found, the XOR decryption function unlocks and prints the flag.
    python
    python3 level4.py

    Expected output

    picoCTF{...}
    What didn't work first

    Tried: Running the original unmodified level4.py and guessing passwords manually from the list

    Each run of the unmodified script accepts exactly one password and then exits, so covering the list means launching the script up to 100 times and retyping by hand. It would eventually work, but it is slow and error prone, and the same effort written once as a loop finishes in milliseconds.

    Tried: Using hashcat or john to crack the stored MD5 hash directly instead of modifying the script

    It works but it is the long way around. The stored hash is raw binary, so you first have to hex-encode it into a format hashcat accepts, then point the attack at a wordlist. The answer is already sitting in pw_list inside the script, so a five-line loop over 100 known candidates beats setting up an external cracking run.

    Learn more

    The XOR decryption used in these pw-crack challenges is a simple symmetric cipher: the flag bytes are XORed with a key derived from the password. XOR has the property that applying it twice with the same key returns the original value - (A XOR K) XOR K = A - making encryption and decryption the same operation.

    While XOR is not secure on its own for real encryption (it is trivially breakable with known-plaintext attacks), it appears frequently in CTF challenges and in obfuscated malware as a lightweight way to obscure data. Recognizing XOR-based encoding and knowing how to reverse it is a valuable skill for reverse engineering challenges.

    The progression from pw-crack-1 through pw-crack-5 mirrors the real evolution of password security practices: plaintext storage, light obfuscation, small hash lookups, larger hash lookups, and finally full dictionary attacks. Each level requires a slightly more sophisticated approach.

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

Testing 100 MD5 hashes takes milliseconds - the same loop logic scales to millions of entries for real dictionary attacks.

Key takeaway

Password hashing turns a secret into a fixed-length digest, and the security rests entirely on the hash being hard to reverse and the password space being large enough to defeat enumeration. Give an attacker a candidate list and the target hash and they simply hash each candidate and compare, which is exactly what John the Ripper and hashcat do at scale. Defending against that takes large, random passwords absent from any wordlist, plus slow hashing like bcrypt or Argon2 to make each test expensive.

Related reading

Useful tools for General Skills

Where to go next