Introduction to Native ARM64 Hooking with Frida
Frida, a dynamic instrumentation toolkit, is an indispensable asset for reverse engineers. While often lauded for its ease of use in hooking Java methods on Android, its true power extends to the native realm, allowing deep inspection and manipulation of ARM64 machine code. This guide will walk you through setting up a dedicated lab and performing advanced native ARM64 hooking on Android using Frida, enabling you to intercept, modify, and understand low-level behaviors within shared libraries.
Setting Up Your Android Reverse Engineering Lab
Before diving into the intricacies of ARM64 hooks, ensure your environment is correctly configured. You’ll need an Android device or emulator with root access, ADB installed on your host machine, and the Frida toolkit.
Requirements:
- Rooted Android Device or Emulator (e.g., AVD, Genymotion, physical phone)
- Android Debug Bridge (ADB) installed and configured on your host
- Python 3 and `pip` for Frida tools
- A basic understanding of ARM64 assembly (beneficial but not strictly required for this tutorial)
Installation Steps:
- Install Frida Tools on Host:
pip install frida-tools - Download Frida Server for Android:Visit Frida’s GitHub releases page and download the `frida-server` binary matching your device’s architecture (typically `arm64`).
- Push Frida Server to Device:
adb push /path/to/frida-server /data/local/tmp/frida-server - Set Permissions and Run Frida Server:
adb shellsu -c "chmod 755 /data/local/tmp/frida-server"su -c "/data/local/tmp/frida-server &"Verify Frida server is running using `adb logcat | grep frida` or `frida-ps -U`. You should see processes listed.
Understanding Native Hooking on ARM64
Unlike Java methods, which are high-level abstractions, native functions directly interact with the CPU and memory. When hooking native ARM64 functions, you’re dealing with raw machine code instructions, register states, and stack frames. Frida’s `Interceptor` API provides a powerful way to insert your code before (`onEnter`) and after (`onLeave`) a target function executes, allowing you to inspect and modify its arguments, registers, and return value.
ARM64 Calling Conventions (Brief Overview)
For functions with up to 8 arguments, ARM64 typically uses registers `x0` to `x7` to pass integer or pointer arguments. The return value is usually placed in `x0`. For more arguments, the stack is used. Floating-point arguments use `v0` to `v7`. Understanding this helps when inspecting `this.context` in Frida hooks.
Identifying Target Functions
To hook a native function, you first need to identify its symbol within a shared library (`.so` file). You can often find these using `nm` or `readelf` on the library file. Let’s create a simple native library `libnativehookme.so` to demonstrate.
Dummy Native Library (`nativehookme.cpp`):
#include <string>#include <vector>#include <iostream>extern "C" __attribute__((visibility("default")))int calculate_xor_checksum(const char* data, size_t len) { if (!data || len == 0) { return 0; } int checksum = 0; for (size_t i = 0; i < len; ++i) { checksum ^= data[i]; } return checksum;}extern "C" __attribute__((visibility("default")))void log_sensitive_data(const char* tag, const char* message, int level) { std::cout << "[NATIVE_LOG] [" << tag << "] [Level: " << level << "] " << message << std::endl;}
Compile this into `libnativehookme.so` for your ARM64 target (e.g., using Android NDK toolchain). Then, push it to your device (e.g., `/data/local/tmp/libnativehookme.so`).
Finding Symbols with `nm`:
adb shellnm -D /data/local/tmp/libnativehookme.so | grep calculate_xor_checksum
You should see something like: `0000000000001234 T calculate_xor_checksum`. The `T` indicates a Text (code) symbol, and the address `0x1234` is its offset within the library.
Basic Native Function Hooking with Frida
Let’s create a simple Frida script to attach to `calculate_xor_checksum`.
`native_hook_checksum.js`:
Java.perform(function () { var targetLibrary = Module.findExportByName(null, "calculate_xor_checksum"); // Initial attempt, might need library name if (!targetLibrary) { // Try finding it within a specific module if the above fails targetLibrary = Module.findExportByName("libnativehookme.so", "calculate_xor_checksum"); } if (targetLibrary) { console.log("[*] Found calculate_xor_checksum at: " + targetLibrary); Interceptor.attach(targetLibrary, { onEnter: function (args) { console.log("[*] Entering calculate_xor_checksum"); console.log(" data (ptr): " + args[0]); console.log(" data (str): " + args[0].readCString()); console.log(" len: " + args[1].toInt32()); }, onLeave: function (retval) { console.log("[*] Leaving calculate_xor_checksum, original return: " + retval); } }); console.log("[*] Hooked calculate_xor_checksum"); } else { console.log("[-] Could not find calculate_xor_checksum"); }});
Run this script using `frida -U -l native_hook_checksum.js –no-pause -f com.your.app.package` (replace `com.your.app.package` with the target app). When your app calls `calculate_xor_checksum`, you’ll see the logs.
Advanced Hooking: Argument and Return Value Manipulation
Frida allows you to not only observe but also modify the execution flow. Let’s modify the input `data` and the `checksum` return value.
`advanced_native_hook.js`:
Java.perform(function () { var targetLibrary = Module.findExportByName("libnativehookme.so", "calculate_xor_checksum"); if (targetLibrary) { console.log("[*] Found calculate_xor_checksum at: " + targetLibrary); Interceptor.attach(targetLibrary, { onEnter: function (args) { this.log_tag = "XOR_HOOK"; this.original_data_ptr = args[0]; this.original_data_len = args[1].toInt32(); this.original_data_str = this.original_data_ptr.readCString(); console.log("[" + this.log_tag + "] Entering calculate_xor_checksum"); console.log(" Original data: " + this.original_data_str); console.log(" Original length: " + this.original_data_len); // Modify the input data var newString = "HOOKED_DATA_BY_FRIDA!"; var newLength = newString.length; // Allocate new memory and write the modified string var newBuffer = Memory.allocUtf8String(newString); args[0] = newBuffer; // Replace the pointer to the data args[1] = new NativePointer(newLength); // Replace the length console.log("[" + this.log_tag + "] Modified input to: " + newString + " (len: " + newLength + ")"); }, onLeave: function (retval) { console.log("[" + this.log_tag + "] Leaving calculate_xor_checksum"); console.log(" Original return value: " + retval); // Modify the return value var modifiedRetval = 0xDEADBEEF; // Custom value retval.replace(new NativePointer(modifiedRetval)); console.log(" Modified return value to: " + retval); } }); console.log("[*] Hooked calculate_xor_checksum for modification"); } else { console.log("[-] Could not find calculate_xor_checksum in libnativehookme.so"); } // Hooking another function for demonstration: log_sensitive_data var logSensitiveDataPtr = Module.findExportByName("libnativehookme.so", "log_sensitive_data"); if (logSensitiveDataPtr) { console.log("[*] Found log_sensitive_data at: " + logSensitiveDataPtr); Interceptor.attach(logSensitiveDataPtr, { onEnter: function (args) { console.log("[LOG_HOOK] Intercepted log_sensitive_data:"); console.log(" Tag: " + args[0].readCString()); console.log(" Message: " + args[1].readCString()); console.log(" Level: " + args[2].toInt32()); // Change the log level to a less severe one args[2] = new NativePointer(1); // Set level to 1 (e.g., INFO) } }); console.log("[*] Hooked log_sensitive_data"); } else { console.log("[-] Could not find log_sensitive_data in libnativehookme.so"); }});
This script demonstrates:
- Reading `NativePointer` arguments as C strings (`readCString()`).
- Allocating new memory (`Memory.allocUtf8String`) and replacing argument pointers.
- Casting arguments to integers (`toInt32()`).
- Modifying the return value using `retval.replace()`.
- Hooking multiple functions within the same script.
When the application calls `calculate_xor_checksum`, Frida will intercept it, modify the input data to
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 →