JNISleuth-MCP
Targets Android APKs and their native libraries: extracts native method declarations from DEX files, maps them to ELF offsets in stripped ARM64/ARMv7/x86-64 .so files, recovers RegisterNatives bindings via static .rodata scanning or on-device Dobby hooking, and reconciles the results into Ghidra/IDA projects.
Exports the JNI analysis tools as native Gemini function-calling schemas via jnisleuth llm-tools --format gemini for use with google-genai based agents.
Provides direct tool-schema export for Python agent SDKs such as LangChain, LlamaIndex and LiteLLM.
Supports local/self-hosted model deployments by exporting tool schemas for Ollama, vLLM, LM Studio and similar serving stacks.
Exports the JNI analysis tools as OpenAI function-calling schemas via jnisleuth llm-tools --format openai for use with the OpenAI SDK and compatible agents.
π΅οΈββοΈ JNISleuth-MCP
Bridge the Java-to-Native Chasm in Android Reverse Engineering.
Automated RegisterNatives Recovery, Stripped ARM64 .so Type Reconstruction, and a Unified Jadx + Ghidra or IDA MCP Server for AI-Assisted Malware Analysis.
Quickstart β’ How It Works β’ MCP Tools β’ Stealth Dobby Agent β’ Roadmap
β‘ Why JNISleuth-MCP?
Modern Android malware, banking trojans, spyware, and commercial packers hide their core logic (C2 encryption, key derivation, environment checks, and exploit routines) inside stripped C/C++ native libraries (lib*.so).
When reversing across the JNI boundary, analysts hit three walls:
Broken Cross-References:
nativemethods declared inclasses.dexdo not use standardJava_com_pkg_Class_methodsymbols. Instead, they are dynamically bound at runtime inside an obfuscatedJNI_OnLoadviaJNIEnv->RegisterNatives().Blind Decompilation: Opening a stripped
.soin Ghidra or IDA shows thousands of unnamedsub_XXXXfunctions whereJNIEnv*vtable calls look like raw pointer arithmetic ((**(code **)(*param_1 + 0x538))(param_1, param_2)).Disconnected AI Context: Existing Ghidra MCP and Jadx MCP servers live in silosβan LLM cannot trace data flow from an Android
BroadcastReceiverin Java straight into an ARM64 encryption routine inlibnative-lib.so.
JNISleuth-MCP eliminates the context switch. It automatically recovers static and dynamic RegisterNatives bindings, re-types stripped ARM64 functions in Ghidra, and exposes a single unified Model Context Protocol (MCP) server connecting DEX bytecode to native assembly.
Capability | Standard Jadx / Ghidra MCPs |
|
Resolve standard | β | β |
Recover stripped | β | β |
Stealth on-device | β | β |
Auto-apply | β | β |
Single-call cross-boundary dataflow ( | β | β |
Related MCP server: Frida Agent MCP
ποΈ Architecture & How It Works
flowchart LR
subgraph Input["π¦ Target APK / Malware"]
DEX["classes*.dex"]
ELF["Stripped lib/*.so (ARM64)"]
end
subgraph Harvester["π Stage 1: JNI Bridge Harvester"]
StaticScan["Static ELF & DEX Scanner\n(LIEF + Capstone + Heuristic Triplets)"]
DobbyAgent["Stealth Native Agent\n(C++ / NDK / Dobby Inline Hook)"]
end
subgraph GhidraEngine["βοΈ Stage 2: Ghidra Headless Reconstructor"]
Renamer["Function Renamer & Signature Applier"]
VTable["JNIEnv* VTable & Struct Propagator"]
end
subgraph MCPServer["π€ Stage 3: Unified FastMCP Server"]
CrossGraph["Cross-Boundary Callgraph\n(Jadx Java + Ghidra C/ARM64)"]
CallbackTrace["Upcall Tracer\n(Native -> Java Reflection)"]
end
DEX --> StaticScan
ELF --> StaticScan
ELF -. "Packed / OLLVM?" .-> DobbyAgent
StaticScan --> Renamer
DobbyAgent -- "register_natives.json" --> Renamer
Renamer --> VTable
VTable --> CrossGraph
VTable --> CallbackTrace㪠See It In Action
1. CLI Static + Dynamic RegisterNatives Recovery
Run jnisleuth scan against an APK with stripped native libraries:
$ jnisleuth scan --apk samples/banker_dropper.apk --arch arm64-v8a
[*] Parsing 3 DEX files... Found 14 declared `native` methods across 4 classes.
[*] Inspecting lib/arm64-v8a/libcore-crypto.so (Stripped ELF, 0 exported Java_* symbols)
[*] Running static JNINativeMethod triplet scanner on .rodata / .data.rel.ro...
[+] Resolved 11/14 methods via static struct table @ 0x0004d210
[!] 3 methods unresolved (constructed dynamically on stack in JNI_OnLoad).
[*] Launching headless Dobby collector on device (emulator-5554)...
ββββββββββββββββββββββββββββββββββββββββββββ³βββββββββββββββββββββββ³ββββββββββββββββββββββ³βββββββββββββββββββ
β Java Class & Method β JNI Signature β .so Offset (ARM64) β Resolution Mode β
β‘βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ©
β com.dropper.Core#initEngine β (Landroid/content/.. β libcore.so+0x001a40 β Static (.rodata) β
β com.dropper.Core#decryptPayload β ([BLjava/lang/Stri.. β libcore.so+0x0023c8 β Static (.rodata) β
β com.dropper.Core#signC2Beacon β (Ljava/lang/String.. β libcore.so+0x003f10 β Dynamic (Dobby) β
β com.dropper.net.SocketBridge#connectRaw β (Ljava/lang/String.. β libcore.so+0x004890 β Dynamic (Dobby) β
ββββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββ΄ββββββββββββββββββββββ΄βββββββββββββββββββ
[+] Synced 14 function signatures and JNIEnv* types to Ghidra project: banker_dropper.gpr2. Before vs. After in Ghidra
Before JNISleuth-MCP (FUN_000123c8):
long FUN_000123c8(long *param_1, undefined8 param_2, undefined8 param_3, undefined8 param_4) {
char *pcVar1;
long lVar2;
pcVar1 = (char *)(**(code **)(*param_1 + 0x548))(param_1, param_4, 0);
lVar2 = (**(code **)(*param_1 + 0x5c0))(param_1, param_3, 0);
sub_00014a00(lVar2, pcVar1);
(**(code **)(*param_1 + 0x550))(param_1, param_4, pcVar1);
return param_3;
}After JNISleuth-MCP (Java_com_dropper_Core_decryptPayload):
jbyteArray Java_com_dropper_Core_decryptPayload(
JNIEnv *env, jobject thiz, jbyteArray encryptedData, jstring rc4KeyStr)
{
const char *key_utf = (*env)->GetStringUTFChars(env, rc4KeyStr, NULL);
jbyte *payload_bytes = (*env)->GetByteArrayElements(env, encryptedData, NULL);
rc4_crypt_buffer(payload_bytes, key_utf);
(*env)->ReleaseStringUTFChars(env, rc4KeyStr, key_utf);
return encryptedData;
}π€ Universal LLM & MCP Support (Works With Any Model)
JNISleuth-MCP is 100% model-agnostic. You can connect it to any cloud or local LLM via the standard Model Context Protocol (stdio JSON-RPC 2.0) or export native OpenAI / Gemini / Ollama function-calling schemas:
Cloud LLMs & Agents: Google Gemini CLI, OpenAI / ChatGPT Desktop / Codex CLI, Cursor, Windsurf, Cline, Roo Code, Claude Desktop / Claude Code
Local / Self-Hosted LLMs: Ollama, vLLM, LM Studio, OpenWebUI, Continue.dev (DeepSeek-R1, Qwen-2.5-Coder, Llama-3.3)
Custom Python Agent SDKs: Direct tool-schema export (
jnisleuth llm-tools --format openai|gemini|mcp) for LangChain, LlamaIndex, LiteLLM, OpenAI SDK, orgoogle-genai.
Tool Name | Description |
| Extracts all |
| Returns the decompiled Java caller (via Jadx) and the typed, decompiled C/ARM64 implementation (via Ghidra/IDA) in a single unified response. |
| Lists every Java reflection callback ( |
| Applies |
| Applies |
| Resolves OLLVM |
| Dumps stack-constructed and XOR-decrypted strings inside |
π₯· Stealth Dobby Agent (Anti-RASP & Packer Mode)
Many wild Android malware families and commercial protectors immediately crash or exit if they detect frida-agent.so in /proc/self/maps, gum-js-loop threads, or modified function prologues in libc.so.
JNISleuth-MCP includes an optional zero-Frida native collector (agent-native/) written in C++20 and ARM64 assembly using Dobby:
Mid-Function Hooking: Places hooks inside the
ArtMethod::RegisterNative/JNIEnvExt::RegisterNativesdispatch path rather than the first instruction of exported stubs, evading basic prologue integrity scanners.Base-Address Normalization: Uses
dladdr()and linkersoinfotraversal to automatically subtract ASLR slide and record deterministic file offsets (libfoo.so + 0x23c8), even if the packer unmaps ELF section headers afterJNI_OnLoad.
π 2-Minute Quickstart
Prerequisites
Python 3.10+ (
pip install uv)Ghidra 11.0+ (
GHIDRA_INSTALL_DIR) or IDA Pro 9.0+ (IDA_INSTALL_DIR/idapyswitch)Jadx CLI installed in
$PATH(Optional) Android NDK r26+ & ADB for dynamic Dobby collection
Installation
git clone https://github.com/<your-username>/JNISleuth-MCP.git
cd JNISleuth-MCP
uv sync
# 1. Scan the included obfuscated NDK crackme sample & sync with Ghidra / IDA Pro 9.0:
uv run jnisleuth scan --apk examples/obfuscated_jni_sample.apk --ghidra-headless --ida-headless
# 2. Resolve & patch OLLVM CSEL + BR X8 indirect branches in JNI_OnLoad:
uv run jnisleuth deobfuscate --so examples/libcore-crypto.so --offset 0x5200 --patch-out examples/libcore-crypto.deobf.soConnect to Any LLM / IDE Agent (mcp.json / settings.json)
Works out of the box with Gemini CLI (~/.gemini/settings.json), Cursor (.cursor/mcp.json), Windsurf, Cline, Continue.dev (Ollama), ChatGPT Desktop, and Claude:
{
"mcpServers": {
"jnisleuth-mcp": {
"command": "uv",
"args": ["run", "--directory", "/path/to/JNISleuth-MCP", "jnisleuth-mcp"],
"env": {
"GHIDRA_INSTALL_DIR": "/opt/ghidra",
"IDA_INSTALL_DIR": "/opt/idapro-9.0"
}
}
}
}Use with OpenAI / Gemini / Ollama Python SDKs Directly (No MCP Client Needed)
# Export function-calling tool schemas for OpenAI / Ollama / vLLM or Google Gemini:
uv run jnisleuth llm-tools --format openai > openai_tools.json
uv run jnisleuth llm-tools --format gemini > gemini_tools.jsonπΊοΈ Roadmap
Static DEX
nativemethod parser + ELF.rodata/.data.rel.roJNINativeMethodtriplet scannerHeadless Ghidra
JNIEnv*vtable auto-typing script for ARM64 & x86_64Unified FastMCP server bridging Jadx, Ghidra & IDA Pro 9.0
On-device Dobby mid-function
RegisterNatives+DexFile::OpenCommonunpacker moduleIDA Pro 9.0 (
idapyswitch/idalib/ headlessidat64) backend support alongside GhidraAutomated OLLVM indirect-branch (
BR X8) resolver & ELF patcher forJNI_OnLoadstate machines
β οΈ Disclaimer
JNISleuth-MCP is developed for defensive malware analysis, mobile application security assessments, and reverse-engineering research. All sample binaries in examples/ are synthetic crackmes created specifically for testing.
Available Tools
12 toolsdeobfuscate_jni_stringsC
Alias for extract_jni_onload_strings.
Inputs: so_name (required), apk_path.
extract_jni_onload_stringsC
Dumps stack-constructed and XOR-decrypted strings inside .init_array and JNI_OnLoad.
Inputs: so_name (required), apk_path.
get_cross_boundary_callgraphD
Alias for get_cross_boundary_slice.
Inputs: java_method (required), apk_path.
get_cross_boundary_sliceC
Returns the decompiled Java caller (via Jadx) and the typed, decompiled C/ARM64 implementation (via Ghidra/IDA) in a single unified response.
Inputs: java_method (required), apk_path.
import_dynamic_traceD
Alias for sync_ghidra_jni_types.
Inputs: trace_json (required), project_name.
list_jni_methodsD
Alias for resolve_apk_jni_map.
Inputs: apk_path (required), arch.
resolve_apk_jni_mapC
Extracts all native declarations from DEX and maps them to exact ELF file offsets in lib/*.so.
Inputs: apk_path (required), arch, dynamic_trace_path.
resolve_ollvm_indirect_branchesC
Resolves OLLVM CSEL + BR X8 state-machine dispatchers and .rodata jump tables in JNI_OnLoad, optionally patching the ELF binary.
Inputs: so_name (required), apk_path, fn_offset, patch_output_path.
sync_ghidra_jni_typesB
Applies JNIEnv*, JavaVM*, JNINativeMethod structs, and recovered function signatures directly into an active or headless Ghidra project.
Inputs: trace_json (required), project_name.
sync_ida_jni_typesC
Applies JNIEnv*, JavaVM*, JNINativeMethod structs, and recovered function signatures into IDA Pro 9.0 (idalib / idapyswitch / idat64).
Inputs: trace_json (required), database_name.
trace_jni_callbacksD
Alias for trace_jni_upcalls.
Inputs: so_name (required), fn_offset (required), apk_path.
trace_jni_upcallsC
Lists every Java reflection callback (FindClass, GetMethodID, CallObjectMethod, SetByteArrayRegion) invoked inside a native ARM64 function.
Inputs: so_name (required), fn_offset (required), apk_path.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelogβ¦
TDQS
Scored across 12 tools
Five of the twelve tools are explicit aliases of other tools, so nearly half the surface duplicates existing functionality. Although the descriptions label the aliases, an agent still has multiple names for the same operation, creating avoidable overlap and misselection risk.
All tool names use snake_case and generally follow a verb_noun pattern, with parallel names such as sync_ghidra_jni_types and sync_ida_jni_types. The main weakness is that aliases use different verbs and resource names for the same underlying operation, which is a minor inconsistency in an otherwise predictable naming scheme.
The nominal count of 12 is still within a reasonable range, but five entries are redundant aliases, leaving only about seven unique capabilities. The surface is artificially inflated rather than genuinely broad.
The core JNI reverse-engineering workflow is covered: mapping native methods, cross-boundary slicing, upcall tracing, type syncing for Ghidra/IDA, OLLVM branch resolution, and string extraction. However, some advertised operations such as callgraph retrieval and dynamic trace import are merely aliases, so an agent expecting those distinct capabilities will hit gaps.
Maintenance
Related MCP Connectors
MCP server for static security analysis of Android source code
Remote MCP for Android CLI agent build gate, structured receipts, audit logs, and reviewer-ready evi
Evidence-oriented REA + Ghidra static-analysis workflows, read-only PE32 and Delphi VMT tools.
AI-security knowledge as MCP: standards-mapped tools (OWASP, NIST, MITRE) for AI agents.
Related MCP Servers
- AlicenseBqualityBmaintenanceA Model Context Protocol server that connects to a custom JADX fork (JADX-AI) and enables local LLMs to interact with decompiled Android app code for live reverse engineering assistance.32840Apache 2.0
- FlicenseAqualityFmaintenanceAn AI-driven Android dynamic analysis tool that enables AI models to perform automated reverse engineering by controlling Frida. It provides capabilities to inject scripts, manage application processes, and analyze real-time execution data through the Model Context Protocol.1070-
- AlicenseAqualityCmaintenanceA multi-backend MCP server that exposes binary analysis capabilities from IDA Pro and Ghidra, allowing LLMs to directly drive reverse-engineering tools via natural language.11177Apache 2.0
- AlicenseNot gradedqualityDmaintenanceLocal static-analysis assistant for Android malware research that manages investigation cases, exposes MCP tools via a local server, and persists evidence-backed findings without cloud dependency.MIT