Description
Reverse this linux executable?
Setup
Download the out binary from the challenge artifacts.
Verify the download succeeded before reaching for upx or Ghidra.
Keep upx, strings, and Ghidra installed; strip is optional but matches the hint.
wget https://artifacts.picoctf.net/c_titan/22/outls -l outstrings out | headSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
entry(), and the Hex Dumps for CTF guide explains the xxd -r -p recipe used in the final step.Step 1
Spot the packerObservationI noticed the challenge was named 'packer' and that running strings on the binary revealed 'UPX!' magic bytes and section names like UPX0 and UPX1, which are the unmistakable fingerprint of UPX packing and suggested running upx -d to decompress before any static analysis.strings out reveals UPX markers. Run upx -d out to unpack, then strip out to remove excess symbols (as hinted).bashupx -d out && strip outExpected output
Ultimate Packer for eXecutables Copyright (C) 1996 - 2024 UPX 4.2.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 3rd 2024 File size Ratio Format Name -------------------- ------ ----------- ----------- 877568 <- 366592 41.78% linux/amd64 out Unpacked 1 file.What didn't work first
Tried: Open the packed binary directly in Ghidra before running upx -d
Ghidra disassembles the UPX decompression stub instead of the real program logic. The decompiler output shows a tight loop moving compressed bytes - none of the actual flag-handling code is visible. You must unpack with upx -d first so Ghidra sees the original ELF sections.
Tried: Run upx -t out to test integrity instead of upx -d to decompress
upx -t verifies that the packed file is intact but leaves it compressed. The binary still executes fine but remains opaque to static analysis. The -d flag (decompress) is required to overwrite the file with the unpacked original.
Learn more
UPX(Ultimate Packer for eXecutables) is an open-source executable packer that compresses a binary's code and data, prepends a small decompression stub, and produces a self-contained file that is typically 50-70% smaller than the original. At runtime, the stub decompresses the original binary into memory and jumps to it, so the packed file executes identically to the original.
Malware authors frequently use UPX (or custom packers inspired by it) to shrink payloads and hinder static analysis - a packed binary's code section looks like compressed data to most disassemblers. The telltale signs of UPX packing visible via
stringsare theUPX!magic bytes and the section namesUPX0andUPX1.upx -d out- decompresses in-place; the file is overwritten with the original ELF.strip out- removes symbol table and debug info, further reducing size (the challenge hints at this step).- If the binary used a custom or modified packer, you would need to dump the process memory at runtime instead.
Step 2
Load into GhidraObservationI noticed the binary was now a fully unpacked ELF after upx -d succeeded, which suggested loading it into Ghidra to decompile the main function and look for any string literals or encoded data that could contain the flag.Analyze the unpacked binary in Ghidra. Open entry() in the decompiler and scan the printf calls; the one whose string literal starts with "7069636f" (hex for "pico") is the flag, encoded as ASCII hex.Learn more
Ghidra is NSA's free, open-source reverse engineering suite. It disassembles machine code into assembly and then decompiles it into readable C-like pseudocode, making it far easier to understand program logic than reading raw bytes. It supports virtually every common CPU architecture and binary format.
The entry() function is the real start of execution in a C program - it calls library initializers and then invokes
main(). In CTF binaries the flag is often embedded as a string literal inmainor a function it calls, where it shows up in Ghidra's decompiled output as a long quoted constant.When you see a long hex literal in decompiled code it is almost always encoded data: a flag, a key, or an obfuscated string. The pattern here - a hex blob inside an executable - is one of the most common beginner reverse-engineering CTF techniques.
Step 3
Convert from hexObservationI noticed Ghidra's decompiler showed a long hex literal inside a printf call whose first bytes decoded to '7069636f' (ASCII 'pico'), which identified it as the flag encoded in plain hex and suggested piping it through xxd -r -p to recover the ASCII string.Pipe the hex string from Ghidra through xxd -r -p (or paste it into CyberChef's From Hex recipe). The resulting ASCII string is the flag.bashecho '<HEX_STRING_FROM_GHIDRA>' | xxd -r -pExpected output
picoCTF{U9X_UnP4ck1N6_B1n4Ri3S_5de...}What didn't work first
Tried: Pass the hex string to xxd without the -p flag: echo '...' | xxd -r
xxd -r without -p expects the canonical xxd dump format (address column + grouped hex + ASCII sidebar). Feeding it a plain continuous hex string produces garbled or empty output because xxd tries to parse addresses from the first characters. The -p flag switches to plain hex mode where the input is purely continuous hex digits.
Tried: Decode the hex with python3 -c "print(bytes.fromhex('...'))" and copy the b'...' repr as the flag
Python's bytes.fromhex prints the bytes object repr including the b'' wrapper and escaped non-printable bytes. The actual flag string is the content inside the quotes, not the full repr. Using .decode() - for example bytes.fromhex('...').decode() - prints the clean ASCII string without the wrapper.
Learn more
Converting from hex to ASCII is one of the most fundamental operations in CTF challenges. Each pair of hex digits maps directly to one ASCII character:
70is 'p',69is 'i',63is 'c', and so on. Recognizing that a string starts with7069636f(= "pico") is a huge hint that you've found the flag encoding.For this challenge, the hex value Ghidra surfaces is:
7069636f4354467b5539585f556e5034636b314e365f42316e34526933535f35646565343434317dFeed that value into
xxd -r -p(the canonical Linux reverse-hex tool:-rreverses,-pmeans plain/continuous hex with no address columns) and the binary output is readable ASCII directly.CyberChef is an excellent alternative for interactive exploration; it also supports chained decodings, so if you encounter hex-within-base64 or similar layering, you can stack recipes to unpack everything in one view.
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.
Flag
Reveal flag
picoCTF{U9X_UnP4ck1N6_B1n4Ri3S_5de...}
Decoding the embedded hex string reveals the flag above.