Athena Wiki

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

Featurex86-64ARM/AArch64
RegistersRAX-R15R0-R12, SP, LR, PC (32-bit) / X0-X30 (64-bit)
ArgumentsRDI, RSI, RDX...R0-R3 (32-bit) / X0-X7 (64-bit)
Return valueRAXR0 / X0
Call instructioncallbl (branch with link)
Link registerReturn addr on stackLR (R14 / X30)
Instruction lengthVariableFixed 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-cross

Debugging 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) continue

ARM 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, X2

Function 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:

  1. Import the ARM binary
  2. Ghidra auto-detects ARM architecture
  3. Auto-analyze (may take longer than x86)
  4. Navigate to main or entry in Symbol Tree
  5. 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 Thumb

Checklist

  • file → confirm ARM 32-bit or AArch64
  • qemu-arm / qemu-aarch64 to 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

On this page