Introduction to ARM64 and Android NDK Reverse Engineering
The Android ecosystem predominantly runs on ARM-based processors, with ARM64 (AArch64) being the architecture of choice for modern devices. While many Android applications are written in Java or Kotlin, performance-critical components, system libraries, and obfuscated code often reside in native libraries compiled with the Native Development Kit (NDK). Understanding ARM64 assembly is paramount for security researchers, reverse engineers, and exploit developers targeting these native binaries. This guide provides a hands-on approach to analyzing ARM64 NDK binaries using industry-standard tools: Ghidra and IDA Pro.
Setting Up Your Analysis Environment
Before diving into assembly, you need a target. NDK binaries are typically packaged as shared libraries (.so files) within an APK. You can extract them as follows:
# Extract an APK to a directory
unzip my_app.apk -d my_app_extracted
# Navigate to the ARM64 library path
cd my_app_extracted/lib/arm64-v8a/
# Identify the native library
ls -l libnative-lib.so
Once extracted, libnative-lib.so becomes our primary target. For this tutorial, we assume a basic understanding of C/C++ and command-line operations.
ARM64 Assembly Fundamentals for Reverse Engineering
ARM64 assembly differs significantly from x86/x64. Key elements to grasp include:
Registers
- General Purpose Registers (X0-X30): 64-bit registers. W0-W30 are their 32-bit counterparts.
- Stack Pointer (SP): Points to the top of the stack.
- Link Register (LR / X30): Stores the return address for function calls.
- Program Counter (PC): Points to the current instruction (implicitly used, not directly accessible as a register).
- Frame Pointer (FP / X29): Used to manage stack frames.
ARM64 Calling Convention (AAPCS64)
The AArch64 Procedure Call Standard (AAPCS64) dictates how functions pass arguments and return values:
- Arguments: The first eight arguments are passed in registers X0-X7 (or W0-W7 for 32-bit values). Additional arguments are pushed onto the stack.
- Return Value: The return value is stored in X0 (or W0).
- Caller-Saved vs. Callee-Saved: Registers X0-X17 are caller-saved (caller must preserve), while X19-X29 are callee-saved (callee must preserve). X18 is a platform register.
Common Instructions
Some frequently encountered instructions:
MOV Xd, Xs: Move value from source register Xs to destination register Xd.ADD Xd, Xn, Xm: Add Xn and Xm, store result in Xd.SUB Xd, Xn, Xm: Subtract Xm from Xn, store result in Xd.LDR Xd, [Xn, #offset]: Load data from memory at address Xn + offset into Xd.STR Xd, [Xn, #offset]: Store data from Xd to memory at address Xn + offset.BL label: Branch with Link. Jumps tolabeland saves the current instruction’s address in LR (X30).B label: Unconditional Branch. Jumps tolabel.RET: Return from subroutine (jumps to the address in LR).STP Xd1, Xd2, [SP, #offset]!: Store Pair. Stores two registers to the stack and updates SP (pre-indexed).LDP Xd1, Xd2, [SP], #offset: Load Pair. Loads two registers from the stack and updates SP (post-indexed).
Ghidra: The Free and Powerful Decompiler
Ghidra, developed by the NSA, is an excellent choice for ARM64 analysis. Its decompiler generates highly readable pseudocode, simplifying complex assembly. Let’s walk through an example:
- Load Binary: Start Ghidra, create a new project, and import
libnative-lib.so. Choose theAARCH64:LE:64:v8Alanguage. Analyze the binary with default options. - Navigate to Functions: In the Symbol Tree or Function List, locate functions of interest. JNI functions often start with
Java_, likeJava_com_example_app_NativeLib_stringFromJNI. - Analyze a Simple Function: Consider a native function that takes two integers, adds them, and returns a string based on the sum.
<code class=
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →