Description
A weird binary compiled from Go. It asks for a password and then for an 'unhashed key'. The Go runtime makes the disassembly large, but the check reduces to recovering one 32-character password by XOR and one short key by cracking an MD5 hash.
Setup
Disassemble the Go binary, recover both answers, then connect and submit them in order.
nc mercury.picoctf.net <PORT_FROM_INSTANCE>Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Locate the two checks in the Go binary
ObservationThe binary asks for two separate inputs: a password, then an unhashed key. That means two independent validation stages in the disassembly, each to be located and analyzed on its own.Load the ELF in Ghidra. Despite the Go noise, the validation is two stages. Stage one ('Enter Password:') XORs your 32-character input against a hardcoded byte array and compares it to a second hardcoded byte array. Stage two ('What is the unhashed key?') takes your input, MD5-hashes it, and compares the digest to a stored hash.bashghidra gogobash# Find the two prompt strings, then the XOR loop and the MD5 comparison.Expected output
reverseengineericanbarelyforward
What didn't work first
Tried: Run 'strings gogo' to find the password in the binary before opening Ghidra.
strings surfaces plenty of Go runtime symbols and even the MD5 digest, but the 32-character password is never stored as a printable string: it is rebuilt at runtime by XORing two arrays. strings only shows data already in ASCII, so the obfuscated bytes appear as garbage or get skipped. Trace the XOR loop in the disassembly to get the plaintext.
Tried: Look for a single 'password check' function and assume both answers come from the same place.
The binary has two independent validation stages at separate call sites. The first XORs your input and compares arrays; the second hashes it with MD5 and compares digests. Treat them as one check and you submit the XOR password at the hash prompt, or the reverse, and both fail. Each prompt wants a different value from a different code path.
Learn more
The binary gates the flag behind two independent answers, and it is easy to conflate them. They are different values for different prompts:
- Password (32 chars): recovered by XORing two hardcoded arrays. This is the value typed at the
Enter Password:prompt. - Unhashed key (short word): the preimage of the stored MD5 hash. This is the value typed at the
What is the unhashed key?prompt.
- Password (32 chars): recovered by XORing two hardcoded arrays. This is the value typed at the
Step 2Recover the 32-character password by XOR
ObservationGhidra shows the first stage XORing your input and comparing it against a second hardcoded byte array. So the plaintext password is just those two stored arrays XORed together.Pull both hardcoded byte arrays out of the disassembly and XOR them together. The result is the 32-character password. (Note that one of the two arrays is the ASCII text of an MD5 hex digest, which is the same hash you crack in the next step.)pythonpython3 - <<'PY' from pwn import xor a = bytes.fromhex("3836313833366631336533643632376466613337356264623833383932313465") b = bytes.fromhex("4a53475d414503545d025a0a5357450d05005d555410010e4155574b45504601") print(xor(a, b).decode()) # -> reverseengineericanbarelyforward PYWhat didn't work first
Tried: XOR only one of the two byte arrays against 0xFF or a guessed key instead of XORing both arrays together.
The obfuscation pairs two arrays so that one XOR the other gives the plaintext. Applying a constant like 0xFF to a single array yields 32 bytes of garbage, because the key is the other array, not a one-byte value. Extract both from the disassembly and XOR them together for the printable password.
Tried: Decode the first byte array directly as ASCII, since it contains hex-like characters.
Decoded as ASCII, the first array spells out the MD5 digest 861836f13e3d627dfa375bdb8389214e, which looks like a password and is not: it is the hash for the second prompt. Enter it at the password prompt and it fails, because the binary compares the XOR result rather than the raw array. XOR both arrays for the real 32-character password.
Learn more
XOR of the two arrays yields
reverseengineericanbarelyforward, which is exactly 32 characters. This is the password for the first prompt. The first array,3836..., is ASCII for the hex string861836f13e3d627dfa375bdb8389214e, which is the MD5 hash you deal with next.Step 3Crack the MD5 hash to get the unhashed key
ObservationThe second stage runs your input through Go's crypto/md5 and compares the digest against the hardcoded 861836f13e3d627dfa375bdb8389214e. Cracking that MD5 against a rainbow table recovers the plaintext preimage.The stored hash is 861836f13e3d627dfa375bdb8389214e. Look it up in a rainbow table (CrackStation) or crack it with hashcat. It resolves to 'goldfish'. That word is the answer to the 'What is the unhashed key?' prompt.bash# Paste 861836f13e3d627dfa375bdb8389214e into crackstation.netbash# or:bashhashcat -m 0 -a 0 861836f13e3d627dfa375bdb8389214e rockyou.txtbash# -> goldfishWhat didn't work first
Tried: Run hashcat with mode -m 1800 (sha512crypt) instead of -m 0 (raw MD5) because the binary is a 'hashed key' comparison.
Mode 1800 targets salted Unix password hashes, not bare MD5 digests, so hashcat rejects the 32-character hex string as an invalid format. The binary calls Go's crypto/md5 on the raw input and stores the digest with no salt and no stretching, which makes mode 0, raw MD5, the right choice.
Tried: Submit the MD5 hex digest 861836f13e3d627dfa375bdb8389214e directly as the answer to the 'What is the unhashed key?' prompt.
The prompt is the hint: it asks for the unhashed key, meaning the preimage. The binary hashes your input and compares that against the stored digest, so submitting the digest itself compares the hash of a hash against the hash, and fails. Only the plaintext 'goldfish' hashes to 861836f13e3d627dfa375bdb8389214e.
Learn more
MD5 of a common word is in every public rainbow table, so this is an instant lookup. The binary stores the digest, not the word, which is why the prompt literally asks you to supply the unhashed key.
Step 4Submit both answers
ObservationBoth values are now in hand: the XOR password 'reverseengineericanbarelyforward' and the MD5 preimage 'goldfish'. All that remains is supplying them to the live service in prompt order.Connect and answer the two prompts in order: the 32-character XOR password first, then 'goldfish'. The service prints the flag.bashnc mercury.picoctf.net <PORT_FROM_INSTANCE>bash# Enter Password: reverseengineericanbarelyforwardbash# What is the unhashed key? goldfish
Interactive tools
- 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.
- 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.
Flag
Reveal flag
picoCTF{p1kap1ka_p1c0...}
Two prompts, two answers. The 32-character password reverseengineericanbarelyforward is the XOR of two hardcoded arrays; the unhashed key goldfish is the MD5 preimage of the stored hash 861836f13e3d627dfa375bdb8389214e. Submit them in order to get the flag.