Bit-O-Asm-2 picoGym Exclusive Solution

Published: March 5, 2024

Description

Follow the memory references in the second disassembly dump to recover the value moved into EAX, then convert it from hexadecimal to decimal.

Fetch the dump and inspect the MOV that assigns DWORD PTR [rbp-0x4] to EAX.

bash
wget https://artifacts.picoctf.net/c/510/disassembler-dump0_b.txt
bash
cat disassembler-dump0_b.txt

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Resolve the stored constant
    Observation
    I noticed that the dump uses DWORD PTR [rbp-0x4] as the source for loading EAX rather than an immediate literal, which suggested I needed to trace back through the function to find what value was written into that stack slot earlier.
    The dump shows 0x9fe1a being written to [rbp-0x4], and that value is later moved into EAX.
    Learn more

    This challenge introduces stack-based local variables. Rather than placing a constant directly into EAX, the compiler first stores the value in a stack slot ([rbp-0x4]) and then loads it back into the register. This pattern is extremely common when the compiler preserves intermediate values across function calls or when the source variable has its address taken.

    RBP is the base pointer register, which typically points to the base of the current stack frame. Negative offsets from RBP (like rbp-0x4) address local variables. DWORD PTR indicates a 4-byte (32-bit) memory access, consistent with writing to a 32-bit variable. Tracing the data flow - from the immediate literal through the stack slot and back into EAX - is the key skill in this step.

    In real reverse engineering, following these data-flow chains is called value tracking or taint analysis. Automated tools (like the taint tracking in Ghidra's decompiler or tools like DynamoRIO) do this programmatically, but manual tracing is essential for understanding what the tools produce and for analyzing obfuscated code where automation struggles.

  2. Step 2
    Convert 0x9fe1a to decimal
    Observation
    I noticed the constant 0x9fe1a written into [rbp-0x4] was in hexadecimal and the flag format requires a decimal integer, which suggested converting the hex value to decimal before constructing the final flag.
    Translate 0x9fe1a into decimal (654874) using printf or python, then format the flag as picoCTF{...}.
    bash
    printf "picoCTF{%d}\n" 0x9fe1a

    Expected output

    picoCTF{654874}
    What didn't work first

    Tried: Using Python with int('0x9fe1a', 16) but forgetting the quotes, writing int(0x9fe1a, 16) instead.

    Python raises TypeError: int() can't convert non-string with explicit base because 0x9fe1a without quotes is already a Python integer literal - the base argument is only valid when the first argument is a string. The fix is either int('9fe1a', 16) with the prefix stripped, or just print(0x9fe1a) directly since Python evaluates the hex literal for you.

    Tried: Running echo 0x9fe1a | bc to convert the hex value.

    bc's default ibase is decimal, so it treats the input as a decimal number and the 0x prefix causes a syntax error or is silently dropped. To use bc for hex conversion you must set ibase=16 and supply the digits in uppercase without the 0x prefix: echo 'ibase=16; 9FE1A' | bc. Using printf or Python avoids this pitfall entirely.

    Learn more

    Converting a multi-digit hex number to decimal requires summing each digit's value multiplied by the appropriate power of 16. For 0x9fe1a: 9×16&sup4; + 15×16³ + 14×16² + 1×16 + 10 = 654874. In practice, no one does this by hand - you reach for printf, Python, or a calculator.

    The printf "%d" 0x9fe1a command works because bash's printf inherits C's integer-literal conventions: the 0x prefix signals a hexadecimal value. Python's built-in int() function similarly accepts a base argument: int("9fe1a", 16) gives the same result. Both are standard tools to keep in muscle memory.

    The broader skill here is recognizing that hex and decimal are representations, not different numbers. The CPU stores a single bit pattern; how humans write it is a matter of convention. Whenever you see a constant in a disassembly listing, ask yourself: is this a count? A character code? An address offset? A bitmask? The representation choice often hints at the intent.

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{...}

Each Bit-O-Asm stage reinforces reading registers and stack slots straight from the disassembly.

Key takeaway

Compilers routinely store values in stack slots (negative offsets from RBP) rather than keeping them in registers, especially when a variable's address is taken or its lifetime spans a function call. Reading assembly means following this store-then-load pattern: find where the value was written into memory, then trace every subsequent read back to that slot. This data-flow tracing skill, sometimes called taint analysis, is the same technique used by reverse engineers auditing license checks, malware analysts tracing C2 commands, and vulnerability researchers locating where attacker-controlled bytes flow through a binary.

Related reading

Want more picoGym Exclusive writeups?

Useful tools for Reverse Engineering

Do these first

What to try next