GDB baby step 2 picoGym Exclusive Solution

Published: March 5, 2024

Description

Continue practicing with debugger0_b by reporting the value in EAX right before main returns. Convert the result to decimal for the final flag.

Debugger practiceDownload debugger0_b

Make the binary executable and load it into gdb with layout asm so you can watch instructions in context.

Place a breakpoint after the final arithmetic instruction (main+59) to read registers at the exact moment main is about to return.

bash
wget https://artifacts.picoctf.net/c/520/debugger0_b
bash
chmod +x debugger0_b
bash
gdb --args ./debugger0_b

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Break after the math
    Observation
    I noticed the binary is stripped (no debug symbols or source), which meant I could not use a line-number breakpoint and had to identify the last instruction that writes EAX from the disassembly and set an offset-based breakpoint at main+59 to capture its final value.
    Inside gdb, set b *(main+59). This lands execution immediately after the last modification of EAX so the register holds its final value.
    bash
    b *(main+59)
    bash
    run
    What didn't work first

    Tried: Setting a breakpoint by source line number such as b main or b 59 instead of the offset-based b *(main+59)

    Without debug symbols or source code GDB cannot resolve line numbers inside a stripped binary, so b 59 either errors out or lands on a completely unrelated location. The asterisk-offset syntax b *(main+59) works directly on the raw instruction address, which is the only reliable anchor in a symbolless binary.

    Tried: Breaking at b *main to stop at the very start of main and then stepping through instructions with ni until EAX looks interesting

    Stepping one instruction at a time through a function with 60+ bytes of arithmetic is tedious and error-prone - it is easy to step past the final write to EAX and land at the ret where EAX may already be used or clobbered. Breaking at exactly main+59 jumps straight to the moment after the last arithmetic operation, giving an unambiguous register snapshot.

    Learn more

    Breakpoints are the primary mechanism for pausing execution at a specific point in GDB. The syntax b *(main+59) sets a breakpoint at the memory address that is 59 bytes into the main function's machine code. The asterisk dereferences the address expression - without it, GDB would try to find a source line rather than an instruction offset.

    Using offset-based breakpoints (rather than source line numbers) is necessary when you do not have debug symbols or source code - exactly the situation in CTF binary challenges. The offset +59 must be determined from the disassembly: look for the last instruction that writes to EAX, count its byte offset from the start of main, and break immediately after it.

    GDB breakpoints are powerful because they support conditions (break main if i == 5), ignore counts (ignore 1 3 skips the first three hits), and commands (commands 1 ... end runs GDB commands automatically when the breakpoint fires). These features let you automate complex debugging scenarios - essential when analyzing loops or deeply nested call chains in real targets.

  2. Step 2
    Print EAX and convert
    Observation
    I noticed the challenge asks for the EAX value right before main returns and requires a decimal result, which suggested using GDB's print $eax command at the breakpoint and converting the output to decimal before wrapping it in picoCTF{}.
    Once the breakpoint hits, run print $eax to capture the register contents. Convert that hexadecimal (if needed) into decimal and wrap it with picoCTF{...}.
    bash
    print $eax

    Expected output

    picoCTF{<eax_decimal>}
    What didn't work first

    Tried: Running p/x $eax to print EAX in hexadecimal and submitting the hex value directly as the flag

    The challenge explicitly asks for the decimal representation. p/x shows a hex string prefixed with 0x, which is a different number when pasted as-is. Running plain p $eax (or p/d $eax) gives the decimal integer that must be wrapped in picoCTF{...}.

    Tried: Using info registers to read EAX before setting a breakpoint and running the binary, reading whatever value appears in the register listing

    info registers at GDB startup reflects uninitialized register state from the shell, not the binary. The meaningful value only appears after the binary has run up to the breakpoint at main+59. Without running the binary first, the EAX readout is meaningless junk from a previous process context.

    Learn more

    In GDB, registers are accessed with a $ prefix: $eax, $rbp, $rip, and so on. The print command (alias p) evaluates and displays an expression. By default it shows the result in decimal, but you can specify a format: p/x $eax for hex, p/t $eax for binary, p/c $eax for the character representation.

    The info registers command (alias i r) dumps all general-purpose registers at once, which is useful when you are not sure which register holds the value you need. For x86-64, the full set includes RAX/RBX/RCX/RDX/RSI/RDI/RSP/RBP and R8-R15, plus the instruction pointer RIP and the flags register EFLAGS.

    After capturing EAX's value, the conversion to decimal is the same as in earlier challenges. The consistent workflow - set breakpoint, run, print register, convert - is deliberately repetitive across the GDB Baby Step series. Repetition builds muscle memory, so that these actions become automatic before the challenges increase in complexity.

Flag

Reveal flag

picoCTF{<eax_decimal>}

Your decimal value depends on the constant embedded in debugger0_b-replace <eax_decimal> with the number you observe in gdb.

Key takeaway

Breakpoints let a debugger pause execution at a precise instruction, giving you a snapshot of register and memory state at exactly the right moment without manually stepping through every preceding instruction. Offset-based breakpoints (b *(main+N)) are essential when no source code or debug symbols are available, because you derive the offset directly from the disassembly rather than from line numbers. This technique is the entry point for dynamic binary analysis, and the same approach is used to intercept license-check comparisons, bypass authentication gates, and catch the exact moment a vulnerability is triggered in exploit development.

Related reading

Want more picoGym Exclusive writeups?

Useful tools for Reverse Engineering

Do these first

What to try next