ARMssembly 1

Published: April 2, 2026

Description

For what argument does this ARM program print 'win'? Flag format: picoCTF{XXXXXXXX} - 8 lowercase hex characters representing the argument as a 32-bit value.

Download chall_1.S.

wget <url>/chall_1.S

Solution

  1. Step 1Trace the arithmetic in the assembly
    Open chall_1.S and read the comparison logic. The program computes a target constant through two operations: it starts with 68, shifts left by 2 (multiply by 4), giving 272, then performs integer division by 3, giving 90. The program prints 'win' when the argument equals 90.
    cat chall_1.S
    Learn more

    Logical shift left (lsl #2) multiplies by 2^2 = 4. Unsigned division (udiv) performs integer division truncating toward zero. So: 68 * 4 = 272, then 272 / 3 = 90 (integer, since 270/3 = 90 exactly). The assembly compares the result with the input argument and branches to the win or lose label accordingly.

    Reading ARM assembly requires recognizing the calling convention: arguments come in w0/x0, and immediate values embedded in instructions are the constants the program uses for its computation. Tracing data flow from input through operations to the final comparison is the core skill.

    Why shift left instead of multiply? On older processors, integer multiplication was a slow operation (multiple clock cycles), while a bitwise shift was a single-cycle instruction. Compilers routinely replace multiplications by powers of two with shift instructions as an optimization. lsl #2 (logical shift left by 2) is equivalent to multiplying by 4, and lsl #3 would be multiply by 8. When reading compiled assembly, always translate shifts back to their multiplication equivalents to understand the original arithmetic.

    ARM vs x86 differences: In x86 assembly, integer division uses the div or idiv instruction, which implicitly reads from and writes to specific registers (eax/edx). ARM's udiv (unsigned divide) takes explicit source and destination register operands, making it easier to trace. The "u" prefix means unsigned - the operands are treated as non-negative integers. The signed equivalent is sdiv. Choosing the wrong variant when analyzing code produces incorrect results if the values could be negative.

    Approach when the logic is more complex: For straightforward arithmetic like this challenge, manual tracing is fastest. For more complicated control flow (nested branches, loops), use a disassembler like Ghidra or radare2. You can cross-compile the .S file with aarch64-linux-gnu-as and run it under qemu-aarch64-static to verify your manual calculation against actual execution.

  2. Step 2Convert the winning argument to 8-digit hex
    The winning argument is 90 decimal. Convert to a 32-bit hex value zero-padded to 8 digits: 0x0000005a.
    python3 -c "print(f'{90:08x}')"

Flag

picoCTF{...}

Follow the shift-left and integer-divide operations: 68*4=272, 272/3=90. The program checks if the argument equals this computed constant.

Want more picoCTF 2021 writeups?

Useful tools for Reverse Engineering

More Reverse Engineering