ARMssembly 2 picoCTF 2021 Solution

Published: April 2, 2026

Description

What integer does this ARM program print with argument 2403814618? Flag format: picoCTF{XXXXXXXX} - 8 lowercase hex characters.

Download chall_2.S.

bash
wget <url>/chall_2.S
  1. Step 1Trace the loop logic
    Open chall_2.S. The loop runs N times (where N is the input) and adds 3 each iteration, starting from 0. That's just input * 3. Assumes -O0 (no optimizer); an optimized build would have folded this to a single mul or a constant.
    bash
    less chall_2.S
    Learn more

    The loop pattern in ARM assembly looks like: initialize a counter and accumulator, compare counter to zero, add the constant, decrement the counter, and branch back if not zero. Recognizing this as repeated addition lets you replace the loop with a single multiplication.

    Working registers on AArch64 are 64 bits wide, but when the result is stored in a w register (32-bit), it is automatically truncated to the lower 32 bits, equivalent to % (2^32). This matters: the mathematical result of 2403814618 * 3 exceeds 2^32 and must be masked.

    Identifying loops in assembly: three characteristic features: an initialization before the loop body, a comparison instruction (cmp or cbz/cbnz), and a backward branch (a branch to a label earlier in the code). When you spot a backward branch, find the loop variable and the accumulator, then count how many times the body runs and what it adds each iteration.

    Why -O0 matters: at -O2 or -O3 the compiler folds this loop into a single multiplication and the verbatim-loop strategy fails. CTF challenges typically ship -O0 binaries to keep the assembly readable, but always sanity-check by skimming for a long loop body before assuming it's a constant-stride accumulator.

  2. Step 2Compute the result with 32-bit truncation
    Multiply the input by 3 and take the lower 32 bits. The full product 7211443854 truncates to 0xadd5e68e.
    python
    python3 -c "print(hex((2403814618 * 3) & 0xffffffff))"
    Learn more

    & 0xffffffff masks to the lower 32 bits, simulating 32-bit integer overflow. In Python, integers are arbitrarily large by default; you must apply the mask explicitly to match the behavior of C uint32_t or ARM w register arithmetic.

    Watching the truncation in binary. 7211443854 is a 33-bit value:

    7211443854 (hex)  = 0x1 ADD5E68E
                        = 1 1010 1101 1101 0101 1110 0110 1000 1110 (binary)
                          ^
                          bit 32 (lost when stored in a 32-bit w register)
    
    after & 0xffffffff:
                           1010 1101 1101 0101 1110 0110 1000 1110
                        = 0xADD5E68E (lowercase: add5e68e)

    The leading 1 is bit 32 and gets discarded by the w-register write. To automate runs across many ARM challenges in one go, see the pwntools guide for connecting QEMU stdio to a Python harness.

Flag

picoCTF{...}

The loop accumulates additions - multiply input by 3 then mask to 32 bits for the wrapped result.

Want more picoCTF 2021 writeups?

Useful tools for Reverse Engineering

Related reading

What to try next