Skip to main content

Bit-O-Asm-4 picoGym Exclusive Solution

Trace a short x86 assembly snippet and compute the final register value to get the flag.

Published: March 5, 2024Updated: August 25, 2026

Description

The fourth assembly dump loads 0x9fe1a, then conditionally subtracts 0x65 (the value exceeds the 0x2710 threshold, so the SUB branch is taken instead of the ADD branch). Convert the result to decimal for the flag.

Fetch the dump and focus on the subtraction at <+31>.

bash
wget https://artifacts.picoctf.net/c/511/disassembler-dump0_d.txt
bash
cat disassembler-dump0_d.txt

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Track the operations
    Observation
    There is a compare against 0x2710, a conditional jump, and two arithmetic branches. Evaluate the actual value against that threshold to see which branch runs before working out what EAX ends up holding.
    The stack slot [rbp-0x4] is assigned 0x9fe1a, then the dump compares that slot against 0x2710 and jumps to an ADD 0x65 branch if the value is less-than-or-equal; because 0x9fe1a (654874) exceeds 0x2710 (10000), the jle is not taken and the SUB 0x65 path runs instead. The final MOV then loads the updated slot into EAX just before the return, and nothing else touches it.
    Learn more

    The SUB instruction subtracts a value from a register or memory location in place. sub DWORD PTR [rbp-0x4],0x65 means [rbp-0x4] = [rbp-0x4] - 0x65, and the value only reaches EAX through the load that follows. Like ADD, SUB modifies flags (carry, overflow, zero, sign) that conditional branches can test - but since this function just returns after the subtract, the flags are irrelevant here.

    A key skill this challenge reinforces is verifying that nothing between the arithmetic and the RET disturbs the value on its way into EAX. In a longer function there might be other instructions that modify the slot or the register - a second store, a CALL that clobbers EAX, or a conditional MOV. Confirming that the final value is indeed the post-subtraction result requires scanning every subsequent instruction for writes to either location.

    This kind of careful instruction-by-instruction analysis is exactly what reverse engineers do when tracing through license-key validation routines, cryptographic transformations, or anti-debugging checks. The habit of asking "does anything modify this value before it reaches the point I care about?" prevents mistakes that could waste hours during a real engagement.

  2. Step 2Compute the result
    Observation
    The subtract branch is the one taken. Let Python do the arithmetic so the hex borrows take care of themselves and the decimal answer comes out clean.
    Evaluate 0x9fe1a - 0x65 = 654,773 and wrap it with picoCTF{...}.
    python
    python3 - <<'PY'
    print(0x9fe1a - 0x65)
    PY

    Expected output

    654773
    What didn't work first

    Tried: Subtract in the other direction: 0x65 - 0x9fe1a, expecting the smaller constant to be the minuend.

    You get a large negative number because the operand order is reversed. The instruction subtracts the constant from the stored value, not the other way round. Keep the destination operand on the left.

    Tried: Take the ADD branch result instead (0x9fe1a + 0x65 = 654,975) because the challenge description mentions both ADD and SUB paths.

    The jump is not taken, because 654874 is greater than 10000, so control falls through to the subtract. The add path runs only when the value is at or below the threshold.

    Learn more

    Subtraction in hex follows the same rules as in decimal, but carries propagate in powers of 16 instead of 10. 0x9fe1a - 0x65: the low byte 1a (26 decimal) minus 65 (101 decimal) would go negative, triggering a borrow, giving b5(181 decimal) in the result's low byte. Letting Python handle this avoids manual borrow tracking.

    Notice that 0x65 is the ASCII code for 'e'. While this is coincidental here, it illustrates how the same number carries different meanings depending on context - always ask whether a constant is meant to be an integer, a character, a flag bitmask, or something else. In this series the constants are pure integers, but in real binaries this ambiguity appears constantly.

    Completing all four Bit-O-Asm challenges builds the core loop of assembly reading: locate the relevant instructions, trace the data flow, apply any arithmetic, and convert to the required representation. This loop scales directly to reading real compiler output, whether you are auditing a closed-source binary, analyzing a firmware image, or studying how a C compiler optimizes a particular expression.

Interactive tools
  • 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.
  • Number Base ConverterConvert numbers between binary, octal, decimal, and hexadecimal instantly. Enter any value and see all four bases update in real time.

Flag

Reveal flag

picoCTF{...}

Double-check the direction of the subtraction: the stored value minus 0x65, not the other way around.

Key takeaway

A compiled if-else becomes a compare plus a conditional jump. The compare performs the subtraction only to set CPU flags, discarding the result, and the jump reads those flags to pick a branch. Knowing which branch runs means evaluating the comparison against the actual register values, not just reading the mnemonic. License checks, authentication bypasses, and anti-debugging routines all hinge on conditional jumps an attacker wants to understand or redirect.

Related reading

Useful tools for Reverse Engineering

Where to go next