Android Native Fuzzing Unleashed: How AFL++ Frida-Mode Exposes 0-Click Bugs in Closed-Source SO Libraries + Video

Listen to this Post

Featured Image

Introduction

The majority of Android’s attack surface lives in native code—.so libraries buried inside APKs, handling everything from image parsing to cryptographic operations. Traditional fuzzing assumes you have source code to instrument with compile-time hooks, but in the real world, the targets that matter almost never ship with symbols or debugging information. AFL++ Frida-mode shatters this limitation by leveraging dynamic binary instrumentation to achieve coverage-guided fuzzing on stripped, closed-source ARM64 binaries, turning the impossible into a repeatable, automated process.

This approach has already proven its worth in the wild. The WhatsApp GIF-Drawable double-free (CVE-2019-11932) and the Samsung Qmage 0-click MMS chain (CVE-2020-8899) were both uncovered using this exact class of native-parser fuzzing. When you cannot recompile, you hook—and when you hook, you find bugs that would otherwise remain invisible.

Learning Objectives

  • Understand how AFL++ Frida-mode enables binary-only coverage-guided fuzzing on Android ARM64 targets without source code access.
  • Build a complete fuzzing harness that targets specific JNI functions within stripped .so libraries using Frida’s dynamic instrumentation.
  • Leverage JNITrace and Medusa to map the JNI attack surface and identify high-signal input paths that lead to memory corruption.
  • Replicate real-world vulnerability discovery workflows used to find CVEs like CVE-2019-11932 and CVE-2020-8899.
  • Deploy and execute fuzzing campaigns directly on Android devices or emulators with performance-optimized configurations.

You Should Know

  1. Why Source-Availability Is the Wrong Assumption for Android Security

Most fuzzing literature assumes you have the source code: compile with -fsanitize-coverage=trace-pc-guard, link against the target, and let AFL++ do its magic. On Android, this assumption fails catastrophically. The native libraries that process untrusted input—image decoders, media parsers, cryptographic routines—are distributed as precompiled `.so` files inside APKs. You do not have the source. You do not have symbols. You have a binary and a prayer.

This is where Frida-mode changes the game. Instead of compile-time instrumentation, Frida attaches to the running process and rewrites code paths dynamically at runtime. AFL++ receives coverage feedback in real time through Frida’s instrumentation callbacks, enabling coverage-guided fuzzing on targets you could never recompile. The fuzzer sees which basic blocks are executed, which branches are taken, and which paths remain unexplored—all without a single line of source code.

The Core Insight: Frida-mode transforms the fuzzing problem from “how do I instrument this?” to “how do I write a harness that calls the right function with the right inputs?” The instrumentation is solved. The challenge is now purely about understanding the target’s API surface.

  1. Building Your First Frida-Mode Harness for a Stripped .SO

The harness is where most of the work lives. You need to write a small C/C++ program that:

  1. Loads the target .so library using `dlopen()` or, on Android, through the JNI environment.
  2. Locates the target function by symbol (if available) or by offset (if stripped).

3. Calls that function with fuzzer-provided input data.

  1. Handles initialization so the library is in a valid state before fuzzing begins.

For stripped libraries, you must determine the function’s offset in the binary. Use a disassembler like IDA Pro or Ghidra to find the function’s starting address, then compute its offset from the module base. At runtime, use `dlopen()` to get the base address and add your offset.

Example Harness Skeleton (C++):

include <dlfcn.h>
include <cstdint>
include <cstring>

// Function pointer type for the target
typedef int (target_func_t)(const uint8_t data, size_t len);

// GNU constructor ensures this runs before main
<strong>attribute</strong>((constructor))
void init() {
// Open the target library
void handle = dlopen("libtarget.so", RTLD_NOW);
if (!handle) return;

// If symbols are stripped, compute address from base + offset
uintptr_t base = (uintptr_t)handle;
uintptr_t offset = 0x12345; // From reverse engineering
target_func_t target = (target_func_t)(base + offset);

// Store for later use in the fuzzing loop
// (Use a global or pass via AFL++ persistent mode)
}

AFL++ Persistent Mode Integration:

// This function is called repeatedly by AFL++ in persistent mode
extern "C" int LLVMFuzzerTestOneInput(const uint8_t data, size_t size) {
// Call your target function with fuzzer input
return target_func(data, size);
}

Compile this harness with the Android NDK for aarch64-linux-android, push it to the device, and run AFL++ with Frida-mode enabled.

  1. Mapping the JNI Surface with JNITrace and Medusa

Native libraries in Android apps are rarely called directly from C/C++ code. Instead, they are invoked through the Java Native Interface (JNI) from the Dalvik/ART runtime. To fuzz effectively, you need to understand which JNI functions are exposed and how they are called.

JNITrace is a Frida-based tool that traces all JNI API calls made by a native library. It works like `strace` but specifically for the JNI environment, showing you every GetStringUTFChars, GetByteArrayElements, CallVoidMethod, and other JNI interactions in real time.

Installation and Basic Usage:

 Install jnitrace (Python package)
pip install jnitrace

Trace JNI calls on a running Android app
jnitrace -m attach -p com.example.app

Medusa takes this further—it is an extensible, modularized binary instrumentation framework built on Frida that automates dynamic analysis of Android applications. Medusa provides over 120 pre-written analysis modules for tracing API calls, monitoring Java and native functions, and automating malware investigation workflows.

Why This Matters for Fuzzing: JNITrace reveals the exact sequence of JNI calls the app makes when processing different inputs. You can identify which native functions are called, what arguments they receive, and how data flows from Java to native code. This intelligence directly informs your harness design—you now know exactly which function to target and what input format it expects.

  1. Step-by-Step: Fuzzing an Android .SO with AFL++ Frida-Mode

This section walks through a complete end-to-end fuzzing setup on an Android device or emulator.

Prerequisites:

  • Android device (rooted) or emulator with ADB access
  • AFL++ compiled with Frida-mode support
  • Frida server running on the Android device
  • Target APK with the .so library you want to fuzz

Step 1: Extract and Prepare the Target Library

 Unzip the APK
unzip target.apk -d target/

Find the ARM64 library
find target/ -1ame ".so" | grep arm64-v8a

Copy it to your working directory
cp target/lib/arm64-v8a/libtarget.so .

Step 2: Reverse Engineer the Function Offset

Open `libtarget.so` in Ghidra or IDA. Locate the function you want to fuzz—look for JNI export names like `Java_com_example_MyClass_nativeMethod` or parser entry points. Note the function’s file offset.

Step 3: Write the Harness

Create `harness.cpp` as shown in Section 2, using the offset you discovered. Compile with the Android NDK:

$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang++ \
-static -o harness harness.cpp

Step 4: Push Files to the Device

adb push harness /data/local/tmp/
adb push libtarget.so /data/local/tmp/
adb push frida-server /data/local/tmp/
adb shell chmod +x /data/local/tmp/frida-server
adb shell /data/local/tmp/frida-server &

Step 5: Run AFL++ with Frida-Mode

 On the host machine, with the device connected via ADB
AFL_FRIDA_PERSISTENT_ADDR=0x12345 \
AFL_FRIDA_PERSISTENT_HOOK=1 \
afl-fuzz -i seeds/ -o output/ -- \
adb shell /data/local/tmp/harness

Key Environment Variables:

  • AFL_FRIDA_PERSISTENT_ADDR: The absolute address of the target function in memory
  • AFL_FRIDA_PERSISTENT_HOOK: Enables persistent mode for faster execution
  • AFL_FRIDA_INST_TRACE: Enables instruction-level tracing for coverage

5. Real-World Case Studies: CVE-2019-11932 and CVE-2020-8899

CVE-2019-11932 – WhatsApp GIF-Drawable Double-Free

This vulnerability resided in the `android-gif-drawable` library before version 1.2.18, as used in WhatsApp for Android. The `DDGifSlurp` function in `decoding.c` contained a double-free that could be triggered by a specially crafted GIF with three frames of size zero. An attacker could send a malicious GIF via WhatsApp, and when the recipient opened the image gallery, the double-free would lead to remote code execution.

The vulnerability was discovered using a black-box fuzzing approach—exactly the kind that AFL++ Frida-mode enables. Without source code, the researcher had to instrument the binary dynamically to find the crash.

CVE-2020-8899 – Samsung Qmage 0-Click MMS Chain

This critical vulnerability affected Samsung devices sold since 2014. The issue was in the custom Qmage image format (.qmg) codec built into Android’s Skia graphics library. A buffer overwrite in the Quram qmg library could be exploited remotely by sending MMS messages.

What made this particularly devastating was the zero-click nature—no user interaction was required. Android automatically processes incoming images to generate thumbnails, so simply receiving an MMS with a malicious Qmage file was enough. The exploit typically required 50 to 300 MMS messages to bypass ASLR, taking about 100 minutes on average.

Both CVEs represent the exact class of bugs that Frida-mode fuzzing is designed to find: memory corruption in closed-source native parsers that process untrusted input.

6. Advanced Techniques: JNI-Specific Harnessing

When targeting JNI functions, your harness must interact with the Java environment. This adds complexity but is essential for testing real-world attack surfaces.

Approach 1: Call JNI Functions Directly

You can call JNI functions from your harness by obtaining the `JNIEnv` pointer and using it to invoke methods. This requires attaching your native thread to the VM:

JavaVM vm;
JNIEnv env;
// Get the VM from the app process
// Attach the current thread
vm->AttachCurrentThread(&env, nullptr);

// Call the JNI function
jclass clazz = env->FindClass("com/example/MyClass");
jmethodID method = env->GetMethodID(clazz, "nativeMethod", "([B)I");
jbyteArray input = env->NewByteArray(size);
env->SetByteArrayRegion(input, 0, size, (jbyte)data);
env->CallIntMethod(obj, method, input);

Approach 2: Fuzz from the Java Side

Alternatively, write a small Java harness that loads the native library and calls the JNI method with byte arrays. This is slower but more realistic, as it exercises the full JNI transition path.

Using JNITrace to Validate Your Harness:

Before running the full fuzzing campaign, use JNITrace to verify that your harness is calling the correct functions with the expected arguments:

jnitrace -m attach -p com.example.harness

You should see the exact JNI calls your harness makes, confirming you are hitting the right code paths.

7. Performance Optimization and Common Pitfalls

Performance Tuning:

Frida-mode instrumentation adds overhead. To maximize execution speed:

  • Use persistent mode (AFL_FRIDA_PERSISTENT_HOOK=1) to reuse the process across fuzzing iterations.
  • Reduce the number of Frida hooks to only what is necessary for coverage.
  • Run on a physical device rather than an emulator for better performance.
  • Use `AFL_FRIDA_OUTPUT=1` to see Frida’s internal logs for debugging.

Common Pitfalls:

  • Invalid Crashes: AFL++ may report crashes that are actually due to harness errors rather than target bugs. Use `frida_mode/DEBUGGING.md` to distinguish between valid and invalid crashes.
  • Symbol Stripping: If the library is stripped, you must compute offsets carefully. Double-check with a disassembler.
  • JNI Environment Issues: Ensure your native thread is properly attached to the Java VM before calling any JNI functions.
  • ASLR: Addresses change between runs. Use relative offsets from the module base rather than absolute addresses.

What Undercode Say

  • Key Takeaway 1: Source-available fuzzing is a luxury most Android security researchers cannot afford. Frida-mode democratizes coverage-guided fuzzing by making any binary instrumentable, regardless of whether symbols or source code are available.

  • Key Takeaway 2: The harness is the critical success factor. Understanding the target’s API surface—through tools like JNITrace and Medusa—is often more important than the fuzzer itself. Signal lives or dies in the harness.

The transition from compile-time instrumentation to runtime hooking represents a paradigm shift in how we approach binary security. Traditional fuzzing assumes cooperation from the target; Frida-mode assumes adversarial conditions. This is not just a technical workaround—it is a philosophical reorientation toward the reality of modern mobile ecosystems, where closed-source native code processes the majority of untrusted data.

The WhatsApp and Samsung CVEs are not anomalies; they are the norm. Every major Android application contains native parsers that have never been fuzzed because the source is unavailable. Frida-mode changes that calculus, putting enterprise-grade fuzzing capabilities into the hands of any researcher willing to write a harness.

However, this power comes with responsibility. The same techniques used to find vulnerabilities can be used to weaponize them. The security community must balance discovery with disclosure, ensuring that fixes reach users before exploits proliferate. The 0-click nature of CVE-2020-8899—requiring only an MMS message—underscores the urgency of proactive fuzzing before attackers find these bugs first.

Prediction

  • +1 Frida-mode will become the default approach for Android native fuzzing within 18 months, as the security community recognizes that source-available targets are the exception, not the rule.

  • +1 Tooling around JNI harness generation will automate much of the current manual reverse engineering, reducing the barrier to entry and dramatically increasing the number of libraries subjected to fuzzing.

  • -1 The ease of Frida-mode fuzzing will also lower the cost of vulnerability discovery for malicious actors, leading to an increase in 0-day exploits targeting closed-source Android components.

  • +1 Platform vendors will respond by deploying more aggressive obfuscation and control-flow integrity measures, sparking an arms race between instrumentation and anti-instrumentation techniques.

  • -1 The MMS attack surface, while largely patched in Samsung devices, remains a template for other zero-click vectors—RCS, iMessage, and WhatsApp itself will see similar classes of bugs discovered via Frida-mode fuzzing in the coming years.

  • +1 Integration of Frida-mode into CI/CD pipelines for Android app security testing will become standard practice, with automated fuzzing runs catching native memory corruption before apps reach production.

  • -1 The performance overhead of Frida-mode will continue to limit its scalability for large-scale, continuous fuzzing campaigns, keeping it primarily in the domain of targeted security research rather than mass-scale auditing.

▶️ Related Video (80% Match):

https://www.youtube.com/watch?v=3DtH1LloBxM

🎯Let’s Practice For Free:

🎓 Live Courses & Certifications:

Join Undercode Academy for Verified Certifications

🚀 Request a Custom Project:

Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands

IT/Security Reporter URL:

Reported By: Pallis Spent – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow UndercodeTesting & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin | 🦋BlueSky