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 1Spot the packer
Observationstrings on the binary turns up the UPX! magic bytes and sections named UPX0 and UPX1. That is UPX packing, so decompress with upx -d 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 stub rather than the program. You get a tight loop shuffling compressed bytes and none of the real logic. Unpack first so Ghidra sees the original 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 2Load into Ghidra
ObservationWith the unpack done, this is an ordinary ELF. Load it in Ghidra, decompile main, and look for string literals or encoded data.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 3Convert from hex
ObservationThe decompiler shows a long hex literal inside a printf, and its first bytes are 7069636f, which is 'pico' in ASCII. That is the flag in plain hex; run it through xxd -r -p.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
Without -p, xxd -r wants its own dump format: address column, grouped hex, ASCII sidebar. Give it a continuous hex string and it reads the first characters as an address and produces garbage. -p switches to plain hex mode.
Tried: Decode the hex with python3 -c "print(bytes.fromhex('...'))" and copy the b'...' repr as the flag
bytes.fromhex prints a bytes repr, complete with the b'' wrapper and escapes for non-printable bytes. The flag is what sits inside the quotes. Call .decode() to get the clean string.
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.