Description
Don't just analyze this binary statically - use dynamic analysis to find the flag. The binary validates input at runtime.
Setup
Download the binary and make it executable.
wget <url>/challchmod +x challSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Try ltrace - it usually leaks the comparison directly
ObservationThe challenge is called 'Let's Get Dynamic' and the description asks for dynamic analysis. ltrace intercepts library calls at runtime, and a dynamically linked binary passes its strcmp arguments through in plaintext.Run the binary under ltrace with dummy input. A line shaped like strcmp("your_input", "the_flag") = 1 reveals the expected value without any reverse engineering. Fall back to strace if the check is implemented as a syscall or read-from-file.bashltrace ./chall <<< 'picoCTF{test}' 2>&1 | head -50bashltrace -e strcmp+memcmp+strncmp ./chall <<< 'picoCTF{test}' 2>&1bashstrace -e openat,read ./chall <<< 'picoCTF{test}' 2>&1 | head -50Expected output
strcmp("picoCTF{test}", "picoCTF{dyn4m1c_4n4ly1s_1s_5up3r_us3ful_...}") = -1What didn't work first
Tried: Run strings on the binary instead of ltrace, hoping the flag is stored as a plain string.
strings lists printable byte sequences, but it will not surface a flag that is assembled at runtime, split across character arrays, or XOR-encoded. ltrace intercepts the strcmp call after all that assembly has happened, so the decrypted value is visible there even when strings finds nothing.
Tried: Use ltrace without filtering and miss the comparison line in pages of output.
Unfiltered ltrace on a verbose binary prints hundreds of allocation and I/O calls, pushing the strcmp line off the visible buffer. Adding -e strcmp+memcmp+strncmp narrows the trace to comparison functions, which makes the flag-revealing line the first and usually the only thing printed.
Learn more
Dynamic analysis tools observe a program's behavior while it is running, rather than reading the binary statically. This is powerful because it bypasses obfuscation: no matter how complex the code, the final comparison must actually happen at runtime, and tracing tools can intercept it.
strace captures system calls - interactions between the program and the kernel. Useful for finding file reads (
openat,read), network activity (connect,send), and process control (fork,execve).ltrace captures calls to shared library functions (libc, libssl, etc.). Useful for finding
strcmp,memcmp, crypto functions, and other library-level comparisons that reveal expected values.Step 2Use GDB to intercept the comparison
Observationltrace produces nothing on a statically linked binary, because there are no shared library calls to intercept. The fallback is GDB: break on the memcmp or strcmp site and read the expected value out of $rsi the moment the comparison fires.If ltrace does not reveal the comparison (e.g., the binary is statically linked), load it in GDB and set breakpoints on comparison instructions. Inspect registers when the breakpoint fires.bashgdb -q ./challbash# (gdb) catch syscall readbash# (gdb) break memcmpbash# (gdb) run <- then type picoCTF{test} at the program's promptbash# At breakpoint: x/s $rdi, x/s $rsiWhat didn't work first
Tried: Set a breakpoint on strcmp by name in GDB expecting it to work like ltrace even on a statically linked binary.
In a dynamically linked binary, GDB resolves strcmp through the PLT and the breakpoint fires. In a static binary the symbol may not exist as a named export, so 'break strcmp' reports the function is not defined. Locate the inlined copy with nm or 'info functions strcmp' and break on the raw address.
Tried: Read flag bytes from $rdi instead of $rsi at the memcmp breakpoint, and see the user-supplied input rather than the expected value.
The x86-64 calling convention puts the first argument in $rdi and the second in $rsi. memcmp receives your input first, so $rdi holds whatever you typed and $rsi holds the expected flag. 'x/s $rsi' is what prints the target value.
Learn more
Breakpoints on common comparison functions: Even in statically linked binaries,
memcmpand similar functions from the C library are still present in the binary (not as dynamic imports, but as compiled-in functions). You can break on them by address after finding them in the symbol table (nm chall | grep memcmp) or by setting breakpoints on the disassembled addresses where the comparison occurs.GDB scripting: For comparisons done in a loop (character by character), use a GDB Python script or commands script to automate: break on the comparison instruction, print the expected byte, continue, and repeat until the full flag is assembled.
Interactive tools
- Cyclic Pattern GeneratorGenerate de Bruijn cyclic patterns and find buffer overflow offsets. The browser equivalent of pwntools cyclic and cyclic_find.
- 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.
- pwntools Payload BuilderPack integers into little-endian bytes (p32 / p64), unpack bytes back to integers, and build flat ROP payloads with offset-based insertion.
Flag
Reveal flag
picoCTF{dyn4m1c_4n4ly1s_1s_5up3r_us3ful_...}
Dynamic analysis (ltrace/strace/GDB) intercepts the comparison at runtime regardless of static obfuscation - the correct value must be present in memory at the moment of comparison.