Description
People keep trying to trick my players with imitation flags. I want to make sure they get the real thing! I'm going to provide the SHA-256 hash and a decrypt script to help you know that my flags are legitimate.
Setup
Download/ssh into the drop-in directory and note checksum.txt, decrypt.sh, and files/.
Have sha256sum and openssl available (both are standard on Linux).
wget https://artifacts.picoctf.net/c_rhea/12/challenge.zip && \
unzip challenge.zip && \
cd drop-inSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Identify the correct file
Observationchecksum.txt holds one SHA-256 hash, and files/ holds dozens of similarly named hex files. Hash them all at once and grep for the match rather than comparing 64-character strings by eye.Hash every file under files/, then grep for the value in checksum.txt. The single line of output names the matching file (files/00011a60 in the canonical drop-in).bashsha256sum files/* | grep 03b52eabed517324828b9e09cbbf8a7b0911f348f76cf989ba6d51acede6d5d8Expected output
03b52eabed517324828b9e09cbbf8a7b0911f348f76cf989ba6d51acede6d5d8 files/00011a60
What didn't work first
Tried: Running sha256sum on just one file and manually comparing the hash string to checksum.txt
One at a time means reading checksum.txt, reading the output, comparing by eye, and repeating for dozens of files. Misreading a 64-character hex string is easy. Hash everything at once and let grep do the comparison.
Tried: Using md5sum files/* instead of sha256sum to find the matching file
checksum.txt holds a 64-character SHA-256 digest. MD5 produces 32 characters, so nothing from md5sum can ever match and the grep comes back empty, as if no file fits. Use the algorithm the checksum was made with.
Learn more
SHA-256 is a cryptographic hash function that takes any input and produces a fixed 256-bit (64 hex character) digest. It has three critical properties: it is deterministic (same input always produces the same hash), collision-resistant (it is computationally infeasible to find two different inputs with the same hash), and one-way (you cannot reverse the hash to find the input).
Hash verification is the standard method for confirming file integrity. When you download software, operating system images, or forensic evidence files, you compare the downloaded file's hash against the expected value to confirm nothing was tampered with or corrupted in transit. This is called a checksum verification.
sha256sum filecomputes the SHA-256 hash of a file.sha256sum files/*computes hashes for all files in the directory - pipe throughgrepto find the matching one.- Other common hash tools:
md5sum(MD5, weak - avoid for security),sha1sum(SHA-1, deprecated),sha512sum(SHA-512, stronger than SHA-256).
Step 2Fix and run the decrypt script
Observationdecrypt.sh ships with the challenge but fails with a 'not a valid file' error. It has a hardcoded path prefix that needs removing; the openssl flags inside it are correct.Open decrypt.sh in a text editor. The script contains a line that prepends/home/ctf-player/drop-in/(or similar) to the argument, creating an invalid path. Remove or comment out that prefix so the script just receives the filename directly. Then run it with the matched file.bashcat decrypt.shbash./decrypt.sh files/00011a60The decrypted output starts with
picoCTF{. If you see a "not a valid file" error, the script is still prepending the broken prefix; edit it and try again.What didn't work first
Tried: Running ./decrypt.sh files/00011a60 without editing the script first
The script glues a hardcoded home-directory path onto your argument, so openssl receives a doubled path when you are already inside that directory. You get a 'not a valid file' error. Open decrypt.sh, drop the prefix line, and run it again.
Tried: Trying to decrypt with openssl enc -d -aes-256-cbc -in files/00011a60 -k picoCTF without the -pbkdf2 and -iter flags
Without -pbkdf2 and the iteration count, openssl falls back to its old key derivation and computes a different key from the same password, so you get binary garbage. Decryption has to mirror the flags used to encrypt, and decrypt.sh lists them.
Learn more
openssl encis OpenSSL's symmetric encryption/decryption command. Breaking down the flags:-dmeans decrypt,-aes-256-cbcspecifies AES-256 in Cipher Block Chaining mode,-pbkdf2uses the PBKDF2 key derivation function (more secure than the old default),-iter 100000runs 100,000 iterations of PBKDF2 to slow brute-force attacks,-saltincludes a random salt, and-k picoCTFprovides the password.PBKDF2 (Password-Based Key Derivation Function 2) deliberately makes password-to-key derivation slow and computationally expensive. If an attacker obtains the ciphertext, they cannot quickly brute-force the password because each guess requires 100,000 hash iterations. Modern alternatives include Argon2 and bcrypt.
The shell script
decrypt.shwraps this command but contains a path-construction bug in this challenge instance. Reading and fixing wrapper scripts is a common CTF skill: the script reveals the exact OpenSSL flags, and removing the broken path prefix is a trivial one-line edit. Always read the provided scripts before running them.Step 3Alternate brute-force
Observationdecrypt.sh already gives away the password, and there are not many files. Loop over all of them with errors suppressed and skip the hash step entirely.Skip the hashing step and just try every file. Suppress stderr (most fail with bad-magic-number errors), pipe throughstringsto keep only printable runs, then grep for the flag prefix.bashfor f in files/*; do openssl enc -d -aes-256-cbc -pbkdf2 -iter 100000 -salt -in "$f" -k picoCTF 2>/dev/null; done | strings | grep picoCTFWhat didn't work first
Tried: Running the brute-force loop without 2>/dev/null, then piping everything including error messages into grep
A failed decryption sends 'bad decrypt' and 'error reading input file' to stderr, and in some shells that text interleaves with the real output, burying the grep result. Redirect stderr to /dev/null so only successful decryptions reach the pipeline.
Tried: Omitting | strings and piping raw openssl output directly into grep for the flag prefix
A failed decryption can emit binary garbage that happens to contain the bytes for picoCTF. Without strings filtering to printable runs, grep matches that junk and points you at the wrong file.
Learn more
The
forloop iterates over every file matchingfiles/*and attempts to decrypt each with the known password. Only the correct file decrypts to readable plaintext; the others fail with an error or produce garbage. Sending stderr to/dev/nullhides the noise so the surviving stdout lines come from the one valid decrypt.This brute-force approach trades CPU time for the convenience of skipping the hash computation step. It is a valid strategy when the file count is small. With hundreds of files and slow PBKDF2 iterations the hash-first approach is faster.
For the broader shell toolkit used here, see the Linux command line guide for CTFs.
Interactive tools
- File Magic IdentifierIdentify file types from magic numbers. Paste hex bytes or drop a file to detect PNG, JPEG, ZIP, PDF, ELF, PCAP, SQLite, and dozens of other formats.
- 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.
- 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.
Flag
Reveal flag
picoCTF{trust_but_verify_0...}
Only the file whose hash matches checksum.txt decrypts to the flag.