ARM Reversing
reversingintermediate
Reverse engineer ARM binaries in CTF challenges - instruction set, tools, and QEMU emulation.
reversingarmarm64aarch64qemughidracross-compile
ARM Reversing
ARM is the dominant architecture for mobile devices (Android, iOS) and embedded systems. CTF challenges increasingly provide ARM binaries. The good news: Ghidra handles ARM natively, so the decompilation workflow is identical to x86. The main differences are in register names, calling conventions, and the need for QEMU emulation to run ARM binaries on x86 hosts.
ARM vs x86-64 Differences
| Feature | x86-64 | ARM/AArch64 |
|---|---|---|
| Registers | RAX-R15 | R0-R12, SP, LR, PC (32-bit) / X0-X30 (64-bit) |
| Arguments | RDI, RSI, RDX... | R0-R3 (32-bit) / X0-X7 (64-bit) |
| Return value | RAX | R0 / X0 |
| Call instruction | call | bl (branch with link) |
| Link register | Return addr on stack | LR (R14 / X30) |
| Instruction length | Variable | Fixed 4 bytes (ARM), 2/4 bytes (Thumb) |
Running ARM on x86 with QEMU
sudo apt install qemu-user qemu-user-static
# Run 32-bit ARM binary
qemu-arm ./arm_binary
# Run 64-bit ARM (AArch64) binary
qemu-aarch64 ./arm64_binary
# With library path (if dynamically linked)
qemu-arm -L /usr/arm-linux-gnueabihf/ ./arm_binary
# Install cross-compilation libraries
sudo apt install gcc-arm-linux-gnueabihf libc6-armhf-crossDebugging ARM with GDB
# Start ARM binary with QEMU in debug mode
qemu-arm -g 1234 ./arm_binary & # listen on port 1234
# Connect GDB with ARM support
gdb-multiarch arm_binary
(gdb) target remote :1234
(gdb) set architecture arm
(gdb) continueARM Assembly Key Instructions
; Data movement
MOV R0, #42 ; R0 = 42
LDR R1, [R2] ; R1 = memory[R2]
STR R0, [SP, #-4] ; memory[SP-4] = R0
; Arithmetic
ADD R0, R1, R2 ; R0 = R1 + R2
SUB R0, R1, #1 ; R0 = R1 - 1
; Bitwise
AND R0, R1, R2 ; R0 = R1 & R2
EOR R0, R1, R2 ; R0 = R1 ^ R2 (XOR!)
LSL R0, R1, #2 ; R0 = R1 << 2
; Comparison & branching
CMP R0, R1 ; set flags based on R0 - R1
BEQ label ; branch if equal
BNE label ; branch if not equal
BL func ; branch to func, LR=return address
; AArch64 variants
MOV X0, #42
LDR X1, [X2]
ADD X0, X1, X2Function Call Conventions (AArch64)
; First 8 args in X0-X7, return in X0
; So strcmp(input, expected):
MOV X0, [input_ptr]
MOV X1, [expected_ptr]
BL strcmp
CBZ X0, correct_branch ; if X0==0, branch (strings equal)Ghidra for ARM
Ghidra handles ARM/AArch64 well out of the box:
- Import the ARM binary
- Ghidra auto-detects ARM architecture
- Auto-analyze (may take longer than x86)
- Navigate to
mainorentryin Symbol Tree - Decompile view works the same as x86
Thumb Mode (ARM 32-bit)
ARM 32-bit binaries often use Thumb (2-byte instructions) for code density:
# In Ghidra: right-click function → Disassemble (ARM/Thumb)
# Check: readelf -s binary | grep ThumbChecklist
-
file→ confirm ARM 32-bit or AArch64 -
qemu-arm/qemu-aarch64to run it - Ghidra handles ARM natively → decompile same as x86
- Key differences: R0=return value, R0-R3=args, BL=call, EOR=XOR
- Debug with
gdb-multiarch+qemu -g 1234 - Thumb mode: 2-byte instructions, check function address LSB
Last updated on