Description
A stripped binary named ret hides a password in plain sight. Retrieve it via static inspection.
Setup
Triage the binary first: file ret tells you the architecture, checksec lists mitigations.
If it looks executable, scan strings with a length filter and grep for pico.
If strings comes up empty, switch to ltrace or gdb (the flag may be built at runtime).
wget https://artifacts.picoctf.net/c/270/retfile ret && checksec --file=retstrings -n 8 ret | grep picoSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Triage with file and checksec
ObservationThe download is a binary named ret, with no extension and no stated format. Confirm the file type and the mitigation profile first so you do not aim the wrong tool at it.file confirms it is an ELF you can inspect, and checksec tells you which mitigations are present. Both run in a fraction of a second and decide your next move.bashfile retbashchecksec --file=retWhat didn't work first
Tried: Skip triage and run strings immediately on the downloaded file.
Some challenge downloads are really archives that report as 'data' rather than ELF. Run strings on a zip and you get hundreds of garbage runs and no flag. One second with file tells you whether there is an ELF here at all.
Tried: Run checksec without first confirming the file is an ELF.
On a non-ELF file, checksec either errors out or reports nonsense, because it is parsing headers that are not there. Run file first to confirm the type, then checksec to read real mitigation fields.
Learn more
Leading with
fileandchecksecis the habit that prevents wasted effort.file retreports architecture, bitness, dynamic vs static, and stripped vs not. If file says "data" instead of ELF, the challenge is probably a packaged blob (zip, tar, custom container) and you are about to waste 10 minutes on the wrong tool.checksec --file=retlists ASLR, stack canaries, NX, PIE, RELRO; even when the challenge is just static recovery, knowing the mitigations is useful for the follow-on challenges.Step 2Filter strings with -n 8 and grep
ObservationStripping removes symbol names, but the string literals in .rodata survive it. So strings with a length filter to cut noise, plus a grep for 'pico', should surface a hard-coded flag with no dynamic analysis at all.strings -n 8 drops short noise like 4-byte symbol fragments. Most flags are at least 12 characters, so an 8-character cutoff keeps real candidates without hiding anything useful.bashstrings ret | wc -lbashstrings -n 8 ret | wc -lbashstrings -n 8 ret | grep picoExpected output
picoCTF{3lf_r...f62bc8}What didn't work first
Tried: Run strings ret without the -n 8 flag and grep pico in the raw output.
The default minimum run is 4 characters, so the output fills with compiler fragments: register names, symbol stubs, table entries. The flag is buried and easy to scroll past. Raising the minimum to 8 cuts the line count enough that a flag-shaped run stands out.
Tried: Use grep -i picoctf instead of grep pico to search the strings output.
A case-insensitive grep costs nothing here, but searching for the wrong prefix does. This competition wraps flags in picoCTF{}, and the literal in .rodata starts with 'picoCTF'. Grepping for pico catches it in four characters; grepping for ctf{ returns nothing.
Learn more
Static analysis examines a binary without executing it. The cheapest static tool is
strings, which extracts printable runs from a binary file. C string literals live in.rodataverbatim, so any hard-coded password, URL, error message, or flag shows up directly instringsoutput.A stripped binary has had its symbol table and debug info removed (via
stripor-s). Stripping hides function names and variable names, but it does not affect string literals stored in the data section. That is whystringsstill works: the flag is a string constant the linker placed in.rodataregardless of whether the binary is stripped.The
-nflag sets the minimum string length (default 4). The before/after counts make the noise reduction concrete: at the default the output is filled with 4-byte fragments from compiler-generated tables; at-n 8only meaningful runs survive, and the flag jumps straight to your eye.-e lhandles 16-bit little-endian (Windows binaries);-e bhandles big-endian.Step 3Decision tree if strings is empty
ObservationIf the grep comes back empty, the flag is built at runtime and handed to a library call. Intercept the strcmp arguments with ltrace before reaching for a decompiler.When strings | grep pico returns nothing, the flag is built at runtime. Use ltrace to catch the comparison, or gdb to break before the strcmp.bashltrace -e strcmp ./retbashltrace ./retWhat didn't work first
Tried: Run ltrace ./ret without filtering and look for the flag manually in the full output.
Unfiltered, ltrace logs every library call: malloc, printf, read, free, memcpy. That is hundreds of lines a second, and the strcmp holding the flag scrolls off the terminal. Filter with -e strcmp and the flag shows up as the second argument on the first matching line.
Tried: Use strace instead of ltrace when strings comes up empty.
strace intercepts kernel syscalls such as open, read, and mmap, not library calls. A strcmp on the password never leaves user space, so strace sees nothing of it. ltrace hooks the C library boundary where strcmp actually lives.
Learn more
When
stringsshows nothing useful, the binary is constructing the flag dynamically: XOR-decoding it from another buffer, building it character-by-character on the stack, or comparing against a hash. Real flags still have to exist in memory at the moment of comparison.ltrace intercepts library calls and prints arguments.
ltrace -e strcmp ./retfilters down tostrcmpcalls; on a typical "guess the password" binary you will seestrcmp("your_input", "real_flag")printed plainly. That is the entire reversal effort, eliminated by knowing which library function the binary uses to compare.When ltrace cannot see the comparison (statically linked binary, custom comparison loop, or stripped symbols), drop into gdb: break on the call to
strcmpor the inline comparison address, run the program with a placeholder input, and read the second argument register on x86_64 (x/s $rsi). Ghidra is the heaviest tool but gives you a decompiled C view of the flag-building logic when the others fall short.
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{3lf_r...f62bc8}
No reversing tools beyond strings are required for this warm-up.