Android Software Reverse Engineering & Decompilation

How To: Static Analysis of Android ARM64 Binaries with Ghidra & IDA Pro

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Android ARM64 Static Analysis

The Android ecosystem relies heavily on native code for performance-critical operations, cryptographic functions, and obfuscation, often implemented using the Native Development Kit (NDK). These native libraries are typically compiled for ARM64 (AArch64) architecture, which is the predominant 64-bit instruction set used in modern Android devices. Static analysis of these ARM64 binaries is a fundamental skill for security researchers, reverse engineers, and malware analysts to understand program logic, identify vulnerabilities, or unravel obfuscated code without executing it. This article will guide you through performing expert-level static analysis using two industry-leading tools: Ghidra and IDA Pro.

Understanding ARM64 assembly is crucial. Key aspects include its register set (31 general-purpose 64-bit registers X0-X30, or W0-W30 for 32-bit operations), specific calling conventions (X0-X7 for arguments, X30 as the Link Register, SP as the Stack Pointer), and instructions for memory access, arithmetic, and control flow.

Setting the Stage: Prerequisites and Tools

Before diving into the analysis, ensure you have the necessary tools and an ARM64 binary to examine. You can extract native libraries (.so files) from an Android Application Package (APK) by unzipping it and navigating to the lib/arm64-v8a/ directory.

  • Ghidra: A free, open-source, powerful software reverse engineering (SRE) suite developed by the NSA.
  • IDA Pro: The industry-standard disassembler and debugger, with its Hex-Rays Decompiler being a standout feature for pseudocode generation.
  • An ARM64 .so binary: Obtained from an APK (e.g., libnative-lib.so).

Analyzing ARM64 with Ghidra: The Open-Source Powerhouse

Loading and Initial Triage

Ghidra provides an intuitive interface for initial binary analysis. Begin by launching Ghidra and creating a new project. Then, import your ARM64 binary:

  1. Go to File > Import File...
  2. Select your .so file. Ghidra will typically auto-detect the architecture (AARCH64) and format (ELF).
  3. Click OK.
  4. After import, double-click the file in the project tree to open it for analysis. Ghidra will prompt you to analyze the binary; accept the default analysis options, ensuring the ‘ELF’ and ‘ARM64’ analyzers are selected.

Once analysis completes, Ghidra’s Code Browser will open, displaying various windows: the Listing (disassembly), Decompiler (pseudocode), Symbol Tree, Functions window, and more.

Deep Dive into ARM64 Assembly in Ghidra

Focus on the Listing (disassembly) and Decompiler windows. Ghidra’s decompiler is excellent for quickly grasping high-level logic, while the assembly view is crucial for understanding precise operations, especially when the decompiler struggles with complex control flow or obfuscation.

Consider a simple function identified by Ghidra, perhaps through an export table or a cross-reference from JNI_OnLoad. Let’s analyze a hypothetical function that adds two 64-bit integers:

// Ghidra Decompiler View (Simplified) 
long add_two_longs(long param_1, long param_2) {
return param_1 + param_2;
}

And its corresponding ARM64 assembly in the Listing View:

             00100000 <add_two_longs>: 
00100000 08 00 80 d2 mov x8, #0x0
00100004 00 00 00 d4 svc #0x0
00100008 c8 00 00 91 add x8, x8, #0x0
0010000c e0 03 00 91 add x0, x0, x1
00100010 c0 03 5f d6 ret

*(Note: The `mov`, `svc`, `add x8` instructions might be prolog/epilog or artifact. The core logic here is `add x0, x0, x1`.)*

In this example:

  • x0 and x1 hold the first and second arguments, respectively, according to ARM64 calling conventions.
  • add x0, x0, x1 performs the addition, storing the result back into x0 (which is the conventional register for return values).
  • ret returns control to the calling function.

Use Ghidra’s cross-reference (X-refs window) to find where functions are called from or where data is accessed. Right-click on a function name or variable and select References > Show References To... to trace its usage.

Mastering ARM64 Analysis with IDA Pro: The Industry Standard

Loading and Initial Setup

IDA Pro, particularly with the Hex-Rays Decompiler, offers unparalleled capabilities. Loading an ARM64 binary is straightforward:

  1. Launch IDA Pro.
  2. Go to File > Open...
  3. Select your .so file. IDA Pro is excellent at automatically detecting file types and architectures.
  4. Click OK. IDA will perform an initial analysis.

After analysis, IDA’s Disassembly View will appear. Press F5 on any function to view its pseudocode in the Hex-Rays Decompiler window.

Advanced ARM64 Analysis Techniques in IDA

IDA Pro’s strengths lie in its comprehensive features for navigating complex codebases, especially with its pseudocode view. Let’s consider analyzing a typical JNI_OnLoad function, which is the entry point for many Android native libraries:

// IDA Pro Hex-Rays Decompiler View 
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
jclass nativeClass;
_JavaVM_GetEnv(vm, &env, JNI_VERSION_1_6);
nativeClass = _JNIEnv_FindClass(env, "com/example/MyNativeLib");
if ( nativeClass ) {
// Register native methods
_JNIEnv_RegisterNatives(
env,
nativeClass,
&methods_0, // Array of JNINativeMethod structures
1 // Number of methods
);
}
return JNI_VERSION_1_6;
}

In the Disassembly View, you would see the ARM64 instructions implementing this logic. To trace the actual native methods, you can perform the following in IDA:

  1. Locate the JNINativeMethod array (e.g., methods_0 in the pseudocode).
  2. Right-click on methods_0 and select Jump to operand or press Ctrl+G.
  3. This will take you to the data segment where the array is defined. Each entry typically contains a method name string, a method signature string, and a function pointer to the native implementation.
  4. Double-click on the function pointer to navigate directly to the ARM64 assembly of the native method (e.g., Java_com_example_MyNativeLib_nativeFunc).

Once inside a native function, you can leverage IDA’s features:

  • Cross-references (X key): See where a function is called from or where a variable is accessed.
  • Graph View (Spacebar): Visualize the control flow of a function, which is invaluable for understanding branches and loops.
  • Renaming (N key): Give meaningful names to functions, variables, and arguments to enhance readability.

Example of ARM64 assembly in IDA’s Disassembly View for a native function:

.text:001000C0                 Java_com_example_MyNativeLib_nativeFunc 
...
.text:001000C0 MOV X2, X1 ; copy string argument
.text:001000C4 BL _ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC1ERKS5_ ; std::string::string(std::string const&)
...
.text:00100100 RET

This snippet shows a common pattern where JNI string arguments (jstring, which becomes _JNIEnv_GetStringUTFChars and then potentially converted to std::string) are passed and used. Analyzing these patterns helps in understanding data manipulation.

Ghidra vs. IDA Pro: When to Use Which

Both Ghidra and IDA Pro are phenomenal tools, each with its strengths:

  • Ghidra:
    • Pros: Free, open-source, excellent for collaborative projects (Ghidra server), robust decompiler, strong scriptability (Python/Java). Ideal for budget-conscious researchers or those preferring open-source solutions.
    • Cons: Can have a steeper learning curve for some, UI might feel less polished than IDA.
  • IDA Pro:
    • Pros: Industry standard, highly mature, superior decompiler (Hex-Rays), extensive plugin ecosystem, powerful debugging capabilities. Often preferred in professional environments.
    • Cons: Expensive license, especially for the Hex-Rays Decompiler.

For Android ARM64 static analysis, a common approach is to start with Ghidra for initial exploration and then switch to IDA Pro (if licensed) for deeper, more complex analysis or when the decompiler accuracy becomes critical.

Tips for Effective Static Analysis

  • Start with Entry Points: For Android native libraries, always begin by examining JNI_OnLoad and any exported JNI functions (e.g., Java_com_example_App_nativeMethod).
  • Identify String References: Search for strings (e.g., API keys, URLs, class names, method names) that can provide context or hints about the binary’s functionality.
  • Understand Calling Conventions: Knowing which registers hold arguments (X0-X7) and return values (X0) is fundamental to interpreting assembly.
  • Rename and Comment: Consistently rename functions, variables, and add comments to document your findings. This is crucial for maintaining clarity in complex binaries.
  • Leverage Cross-References: Trace data and code flow using cross-references to understand how different parts of the binary interact.
  • Be Patient: Reverse engineering is often a meticulous process that requires patience and a systematic approach.

Conclusion

Static analysis of Android ARM64 binaries is an indispensable skill in modern software security. Both Ghidra and IDA Pro offer robust capabilities for this task, each with its unique advantages. By mastering the fundamentals of ARM64 assembly and leveraging the powerful features of these tools—from Ghidra’s open-source accessibility to IDA Pro’s industry-standard decompilation—you can effectively unravel complex native code, identify vulnerabilities, and gain deep insights into Android applications. Continuous practice and exploration of different binaries will further hone your skills in this fascinating domain.

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 →
Google AdSense Inline Placement - Content Footer banner