Let's get dynamic picoCTF 2021 Solution

Published: April 2, 2026

Description

Don't just analyze this binary statically - use dynamic analysis to find the flag. The binary validates input at runtime.

Download the binary and make it executable.

bash
wget https://mercury.picoctf.net/static/.../chall
bash
chmod +x chall

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
Companion reading: GDB for CTF walks through the breakpoint-on-strcmp idiom used here when ltrace alone isn't enough.
  1. Step 1
    Try ltrace - it usually leaks the comparison directly
    Observation
    I noticed the challenge is titled 'Let's Get Dynamic' and the description explicitly says to use dynamic analysis, which suggested intercepting library calls at runtime with ltrace since dynamically linked binaries pass strcmp arguments in plaintext to the trace.
    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.
    bash
    ltrace ./chall <<< 'picoCTF{test}' 2>&1 | head -50
    bash
    ltrace -e strcmp+memcmp+strncmp ./chall <<< 'picoCTF{test}' 2>&1
    bash
    strace -e openat,read ./chall <<< 'picoCTF{test}' 2>&1 | head -50

    Expected output

    strcmp("picoCTF{test}", "picoCTF{dyn4m1c_4n4ly1s_1s_5up3r_us3ful_...}") = -1
    What didn't work first

    Tried: Run strings on the binary instead of ltrace, hoping the flag is stored as a plain string.

    strings ./chall will list printable byte sequences but won't necessarily surface the flag if it is assembled at runtime, stored as separate character arrays, or XOR-encoded. ltrace intercepts the actual strcmp call after any in-memory assembly happens, so the decrypted or concatenated value is visible there even when strings produces nothing useful.

    Tried: Use ltrace without filtering and miss the comparison line in pages of output.

    Unfiltered ltrace on a verbose binary can print hundreds of memory-allocation and I/O calls, pushing the strcmp line off the visible buffer. Adding -e strcmp+memcmp+strncmp narrows the trace to comparison functions only, making the flag-revealing line the first - and usually 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.

  2. Step 2
    Use GDB to intercept the comparison
    Observation
    I noticed ltrace may produce no output if the binary is statically linked (no shared library calls to intercept), which suggested falling back to GDB to set a breakpoint on the memcmp or strcmp site and read the expected flag value from the $rsi register at 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.
    bash
    gdb -q ./chall
    bash
    # (gdb) catch syscall read
    bash
    # (gdb) break memcmp
    bash
    # (gdb) run 'picoCTF{test}'
    bash
    # At breakpoint: x/s $rdi, x/s $rsi
    What 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 via the PLT and the breakpoint fires correctly. In a statically linked binary the symbol may not exist as a named export, so 'break strcmp' produces 'Function not defined'. The fix is to locate the inlined copy with 'nm chall | grep strcmp' or 'info functions strcmp' and break on the raw address instead.

    Tried: Read flag bytes from $rdi instead of $rsi at the memcmp breakpoint, and see the user-supplied input rather than the expected value.

    By x86-64 calling convention the first argument is in $rdi and the second is in $rsi. memcmp(user_input, expected, len) places the user's string in $rdi, which is whatever you typed. The expected flag sits in $rsi. Printing 'x/s $rsi' is what reveals the target value.

    Learn more

    Breakpoints on common comparison functions: Even in statically linked binaries, memcmp and 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.

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.

Key takeaway

Dynamic analysis defeats obfuscation by observing a program at runtime rather than decoding its logic statically. No matter how convoluted the validation code looks in a disassembler, the correct secret must exist in memory at the moment it is compared against the user's input, and tools like ltrace and GDB can intercept that moment directly. The same principle applies to malware analysis, license-check bypasses, and anti-cheat research, where running the target in a controlled environment often reveals secrets faster than static reverse engineering.

Related reading

Want more picoCTF 2021 writeups?

Useful tools for Reverse Engineering

What to try next