Description
What integer does this program print with arguments 2541761492 and 4030728319? Analyze the ARM assembly to compute the output.
Setup
Download the ARM assembly source file.
wget https://mercury.picoctf.net/static/.../chall_3.Scat chall_3.SSolution
Walk me through it- Step 1Read the source: a tiny dispatch into func1 and func2main calls atoi(argv[1]) once, passes the result to func1, and prints whatever func1 returns. The second argument the prompt mentions is unused. func1 is the loop you need to translate, func2 is a one-liner.bash
cat chall_3.S | sed -n '/^func1:/,/.size\s*func1/p'bashcat chall_3.S | sed -n '/^func2:/,/.size\s*func2/p'Learn more
The relevant excerpt of
func1:func1: str w0, [x29, 28] ; n = arg str wzr, [x29, 44] ; result = 0 b .L2 .L4: ldr w0, [x29, 28] and w0, w0, 1 ; if (n & 1) cbz w0, .L3 ldr w0, [x29, 44] bl func2 ; result = func2(result) str w0, [x29, 44] .L3: ldr w0, [x29, 28] lsr w0, w0, 1 ; n >>= 1 str w0, [x29, 28] .L2: ldr w0, [x29, 28] cbnz w0, .L4 ; while (n != 0) ldr w0, [x29, 44] retfunc2is justx + 3.Translated: walk the bits of
nfrom low to high. Each set bit contributes one call tofunc2(result), which adds 3. So the final return value ispopcount(n) * 3.See the Ghidra reverse engineering guide if you want the GUI walkthrough instead.
- Step 2Compute the answer in one Python lineThe result is bin(arg).count('1') * 3 with 32-bit masking. Pass your instance's argument and read the printed hex.python
python3 -c "arg = 2541761492; r = (bin(arg & 0xFFFFFFFF).count('1') * 3) & 0xFFFFFFFF; print(f'decimal={r} hex={r:#010x}')"bash# arg=2541761492 -> 42 (0x0000002a)bash# arg=4030728319 -> 39 (0x00000027) (in case your instance uses this one)Learn more
Why 32-bit masking matters here: ARMv8
w0is a 32-bit register, andaddwraps modulo 232.popcount * 3for any 32-bit input maxes at32 * 3 = 96, so the wrap doesn't kick in. The mask is defensive boilerplate; copy it because you'll need it on the next ARMssembly challenge where wrapping does matter.Alternative: just emulate the binary. If hand-translating feels brittle, build and run with QEMU:
aarch64-linux-gnu-gcc -static -o chall_3 chall_3.S && qemu-aarch64 ./chall_3 2541761492. The output lineResult: Nis your answer; convert N to 8-hex-digit lowercase for the flag.See Python for CTF for more reverse-engineering one-liners and Pwntools for CTF for QEMU automation.
Flag
picoCTF{...}
func1 is popcount(n) * 3 and func2 just adds 3. For arg=2541761492, popcount=14, so result=42 in decimal. Different picoCTF instances seed different inputs; plug yours into the Python one-liner and submit the lowercase 8-hex-digit form.