hacktricks/src/mobile-pentesting/android-app-pentesting/reversing-native-libraries.md
HackTricks News Bot f302c48010 Add content from: CoRCTF 2025 — CoRPhone: Android Kernel Pwn
- Remove searchindex.js (auto-generated file)
2025-09-09 01:31:50 +00:00

8.3 KiB
Raw Blame History

Reversing Native Libraries

{{#include ../../banners/hacktricks-training.md}}

For further information check: https://maddiestone.github.io/AndroidAppRE/reversing_native_libs.html

Android apps can use native libraries, typically written in C or C++, for performance-critical tasks. Malware creators also abuse these libraries because ELF shared objects are still harder to decompile than DEX/OAT byte-code.
This page focuses on practical workflows and recent tooling improvements (2023-2025) that make reversing Android .so files easier.


Quick triage-workflow for a freshly pulled libfoo.so

  1. Extract the library
    # From an installed application
    adb shell "run-as <pkg> cat lib/arm64-v8a/libfoo.so" > libfoo.so
    # Or from the APK (zip)
    unzip -j target.apk "lib/*/libfoo.so" -d extracted_libs/
    
  2. Identify architecture & protections
    file libfoo.so        # arm64 or arm32 / x86
    readelf -h libfoo.so  # OS ABI, PIE, NX, RELRO, etc.
    checksec --file libfoo.so  # (peda/pwntools)
    
  3. List exported symbols & JNI bindings
    readelf -s libfoo.so | grep ' Java_'     # dynamic-linked JNI
    strings libfoo.so   | grep -i "RegisterNatives" -n   # static-registered JNI
    
  4. Load in a decompiler (Ghidra ≥ 11.0, IDA Pro, Binary Ninja, Hopper or Cutter/Rizin) and run auto-analysis.
    Newer Ghidra versions introduced an AArch64 decompiler that recognises PAC/BTI stubs and MTE tags, greatly improving analysis of libraries built with the Android 14 NDK.
  5. Decide on static vs dynamic reversing: stripped, obfuscated code often needs instrumentation (Frida, ptrace/gdbserver, LLDB).

Dynamic Instrumentation (Frida ≥ 16)

Fridas 16-series brought several Android-specific improvements that help when the target uses modern Clang/LLD optimisations:

  • thumb-relocator can now hook tiny ARM/Thumb functions generated by LLDs aggressive alignment (--icf=all).
  • Enumerating and rebinding ELF import slots works on Android, enabling per-module dlopen()/dlsym() patching when inline hooks are rejected.
  • Java hooking was fixed for the new ART quick-entrypoint used when apps are compiled with --enable-optimizations on Android 14.

Example: enumerating all functions registered through RegisterNatives and dumping their addresses at runtime:

Java.perform(function () {
  var Runtime = Java.use('java.lang.Runtime');
  var register = Module.findExportByName(null, 'RegisterNatives');
  Interceptor.attach(register, {
    onEnter(args) {
      var envPtr  = args[0];
      var clazz   = Java.cast(args[1], Java.use('java.lang.Class'));
      var methods = args[2];
      var count   = args[3].toInt32();
      console.log('[+] RegisterNatives on ' + clazz.getName() + ' -> ' + count + ' methods');
      // iterate & dump (JNI nativeMethod struct: name, sig, fnPtr)
    }
  });
});

Frida will work out of the box on PAC/BTI-enabled devices (Pixel 8/Android 14+) as long as you use frida-server 16.2 or later earlier versions failed to locate padding for inline hooks.

Process-local JNI telemetry via preloaded .so (SoTap)

When full-featured instrumentation is overkill or blocked, you can still gain native-level visibility by preloading a small logger inside the target process. SoTap is a lightweight Android native (.so) library that logs the runtime behavior of other JNI (.so) libraries within the same app process (no root required).

Key properties:

  • Initializes early and observes JNI/native interactions inside the process that loads it.
  • Persists logs using multiple writable paths with graceful fallback to Logcat when storage is restricted.
  • Source-customizable: edit sotap.c to extend/adjust what gets logged and rebuild per ABI.

Setup (repack the APK):

  1. Drop the proper ABI build into the APK so the loader can resolve libsotap.so:
    • lib/arm64-v8a/libsotap.so (for arm64)
    • lib/armeabi-v7a/libsotap.so (for arm32)
  2. Ensure SoTap loads before other JNI libs. Inject a call early (e.g., Application subclass static initializer or onCreate) so the logger is initialized first. Smali snippet example:
    const-string v0, "sotap"
    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
    
  3. Rebuild/sign/install, run the app, then collect logs.

Log paths (checked in order):

/data/user/0/%s/files/sotap.log
/data/data/%s/files/sotap.log
/sdcard/Android/data/%s/files/sotap.log
/sdcard/Download/sotap-%s.log
# If all fail: fallback to Logcat only

Notes and troubleshooting:

  • ABI alignment is mandatory. A mismatch will raise UnsatisfiedLinkError and the logger wont load.
  • Storage constraints are common on modern Android; if file writes fail, SoTap will still emit via Logcat.
  • Behavior/verbosity is intended to be customized; rebuild from source after editing sotap.c.

This approach is useful for malware triage and JNI debugging where observing native call flows from process start is critical but root/system-wide hooks arent available.


See also: inmemory native code execution via JNI

A common attack pattern is to download a raw shellcode blob at runtime and execute it directly from memory through a JNI bridge (no ondisk ELF). Details and readytouse JNI snippet here:

{{#ref}} in-memory-jni-shellcode-execution.md {{#endref}}


Recent vulnerabilities worth hunting for in APKs

Year CVE Affected library Notes
2023 CVE-2023-4863 libwebp ≤ 1.3.1 Heap buffer overflow reachable from native code that decodes WebP images. Several Android apps bundle vulnerable versions. When you see a libwebp.so inside an APK, check its version and attempt exploitation or patching.
2024 Multiple OpenSSL 3.x series Several memory-safety and padding-oracle issues. Many Flutter & ReactNative bundles ship their own libcrypto.so.

When you spot third-party .so files inside an APK, always cross-check their hash against upstream advisories. SCA (Software Composition Analysis) is uncommon on mobile, so outdated vulnerable builds are rampant.


  • Pointer Authentication (PAC) & Branch Target Identification (BTI): Android 14 enables PAC/BTI in system libraries on supported ARMv8.3+ silicon. Decompilers now display PACrelated pseudo-instructions; for dynamic analysis Frida injects trampolines after stripping PAC, but your custom trampolines should call pacda/autibsp where necessary.
  • MTE & Scudo hardened allocator: memory-tagging is opt-in but many Play-Integrity aware apps build with -fsanitize=memtag; use setprop arm64.memtag.dump 1 plus adb shell am start ... to capture tag faults.
  • LLVM Obfuscator (opaque predicates, control-flow flattening): commercial packers (e.g., Bangcle, SecNeo) increasingly protect native code, not only Java; expect bogus control-flow and encrypted string blobs in .rodata.

Resources

References

{{#include ../../banners/hacktricks-training.md}}