PW Crack 5 Beginner picoMini 2022 Solution

Published: April 2, 2026

Description

No candidate list this time - a large dictionary file is provided. Read each word, hash it with MD5, and compare it to the stored hash to find the password.

Download level5.py (the checker script) and dictionary.txt (the wordlist).

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Understand the script
    Observation
    I noticed the challenge provided a separate dictionary.txt file rather than a hard-coded candidate list, which suggested this was a classic dictionary attack and that understanding level5.py's existing hash-comparison structure was necessary before extending it.
    Open level5.py. The stored MD5 hash is visible. The script needs to be extended to read each line from dictionary.txt, strip whitespace, hash it, and compare.
    Learn more

    This challenge is a full dictionary attack - the most common real-world technique for cracking hashed passwords. Instead of a curated short list, you now have a wordlist file that must be read line by line and each entry tested against the target hash.

    Wordlists (also called dictionaries) are files containing commonly used passwords, words, phrases, and variations. The most famous real-world wordlist is rockyou.txt, which contains over 14 million passwords leaked from the RockYou social gaming breach in 2009. Security professionals use it as a baseline for password auditing - if a password appears in rockyou.txt, it is considered trivially crackable.

    The key difference from pw-crack-4 is that you do not know the candidate list in advance - you must read it from disk. This is the standard approach for any serious password cracking: maintain a large wordlist file and stream through it programmatically.

  2. Step 2
    Modify the script to loop through the dictionary file
    Observation
    I noticed that level5.py already had the MD5 hash-comparison logic but no file-reading loop, and that Python's file.readlines() attaches a trailing newline to every entry, which suggested adding a loop with line stripping so each candidate is hashed cleanly before comparison.
    Open level5.py and add code that reads dictionary.txt line by line. Each line includes a trailing newline character - remove it before hashing. Use line[:-1] (slice off the last character) or line.strip(). When the hash matches, set user_pw to that word and break.
    Learn more

    When Python reads a line from a file, each line includes the trailing newline character (\n). In ASCII, newline is character 10. Hashing a word with the newline attached produces a completely different MD5 than hashing the word alone - so every single comparison fails if you do not remove it first.

    Two equivalent ways to remove the trailing newline:

    • line[:-1] - slice notation that takes everything except the last character
    • line.strip() - removes all leading and trailing whitespace including newlines

    The loop to add inside level5.py looks like:

    with open('dictionary.txt') as f:
        lines = f.readlines()
    for pw in lines:
        pw = pw[:-1]
        if hash_pw(pw) == correct_pw_hash:
            user_pw = pw
            user_pw_hash = hash_pw(pw)
            break

    The time complexity of this attack is O(n) where n is the number of words in the dictionary. Professional cracking tools add GPU acceleration and multiple worker threads - hashcat on a modern GPU can test tens of billions of MD5 hashes per second. The fundamental algorithm is identical.

  3. Step 3
    Run with the correct password
    Observation
    I noticed that once the dictionary loop was in place the script could iterate through dictionary.txt and find '9581' as the matching MD5 preimage, which suggested running the modified script would feed that recovered password into the existing XOR decryption function and print the flag.
    Once '9581' is identified, use it with the XOR decryption function to print the flag.
    python
    python3 level5.py

    Expected output

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

    Tried: Running the unmodified level5.py before adding the dictionary-reading loop

    The original script has no loop over dictionary.txt, so user_pw stays at its default empty value and the hash comparison never matches. The script exits without printing a flag. You must add the file-reading loop that iterates over each line, strips the newline, hashes it, and sets user_pw when the hash matches.

    Tried: Using line.split() instead of line[:-1] or line.strip() to remove the trailing newline before hashing

    line.split() splits on all whitespace and returns a list, so pw becomes ['9581'] instead of '9581'. Passing a list to hash_pw() causes a TypeError because hashlib.md5() expects a string or bytes, not a list. The correct fix is line[:-1] (slice off the last character) or line.strip(), both of which return a string with the newline removed.

    Learn more

    Completing this challenge demonstrates a complete understanding of the dictionary attack pipeline:

    • Identify the target hash and the hashing algorithm used
    • Select or obtain an appropriate wordlist
    • Stream the wordlist, hash each candidate, compare to the target
    • Use the recovered password to access the protected resource

    The real-world defense against dictionary attacks is salting: prepending or appending a unique random value (the salt) to each password before hashing. The salt is stored alongside the hash. Even if two users have the same password, their hashes are different due to different salts. Salting also defeats precomputed hash tables (rainbow tables) because each salt requires its own precomputed table.

    Modern password hashing algorithms like bcrypt and Argon2 automatically handle salting and are designed to be computationally expensive, making them far more resistant to dictionary attacks than raw MD5. If you ever design a system that stores user passwords, always use one of these purpose-built algorithms - never raw MD5 or SHA.

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

Each line read from a file carries a trailing newline - use line[:-1] or line.strip() to remove it before hashing, otherwise every comparison fails.

Key takeaway

Dictionary attacks succeed because people reuse predictable passwords, and any list of common passwords can be hashed offline and compared against a stolen hash database in seconds. Raw MD5 and SHA hashes offer no resistance because modern GPUs can test billions of candidates per second. Salting each password with a unique random value defeats precomputed rainbow tables, and purpose-built slow hashing functions like bcrypt and Argon2 force attackers to spend real time on every single candidate, making large-scale cracking economically impractical.

Related reading

Want more Beginner picoMini 2022 writeups?

Useful tools for General Skills

What to try next