
Récupération automatisée des RegisterNatives JNI Android, reconstruction des types des .so ARM64 strippés, désobfuscateur OLLVM BR X8 et pont universel LLM/MCP pour Jadx, Ghidra et IDA Pro 9.0.
Combler le fossé Java-natif dans le reverse engineering Android.
Récupération automatisée de RegisterNatives, reconstruction de types pour .so ARM64 strippés, et un serveur MCP unifié Jadx + Ghidra ou IDA pour l'analyse de malware assistée par IA.
Démarrage rapide • Fonctionnement • Outils MCP • Agent furtif Dobby • Feuille de route
Les malwares Android modernes, les chevaux de Troie bancaires, les logiciels espions et les packers commerciaux cachent leur logique centrale (chiffrement C2, dérivation de clés, vérifications d'environnement et routines d'exploitation) à l'intérieur de bibliothèques natives C/C++ strippées (lib*.so).
Lors du reverse engineering à travers la frontière JNI, les analystes se heurtent à trois obstacles :
native déclarées dans classes.dex n'utilisent pas les symboles standard Java_com_pkg_Class_method. Elles sont liées dynamiquement à l'exécution à l'intérieur d'un JNI_OnLoad obfusqué via JNIEnv->RegisterNatives()..so strippé dans Ghidra ou IDA révèle des milliers de fonctions sub_XXXX sans nom où les appels à la vtable de JNIEnv* ressemblent à de l'arithmétique de pointeurs brute ((**(code **)(*param_1 + 0x538))(param_1, param_2)).BroadcastReceiver Android en Java directement jusqu'à une routine de chiffrement ARM64 dans libnative-lib.so.JNISleuth-MCP élimine le changement de contexte. Il récupère automatiquement les liaisons RegisterNatives statiques et dynamiques, retype les fonctions ARM64 strippées dans Ghidra, et expose un unique serveur unifié Model Context Protocol (MCP) reliant le bytecode DEX à l'assembleur natif.
| Capacité | MCP Jadx / Ghidra standards | JNISleuth-MCP |
|---|---|---|
Résoudre les exports standard Java_* | ✅ | ✅ |
Récupérer statiquement les tableaux de structures RegisterNatives strippés (.rodata / .data.rel.ro + CFG de JNI_OnLoad) | ❌ | ✅ |
Collecte furtive sur l'appareil des RegisterNatives via Dobby (contourne les vérifications anti-Frida) | ❌ | ✅ |
Appliquer automatiquement les types de vtable JNIEnv* et reconstruire Call*Method / GetStringUTFChars dans Ghidra | ❌ | ✅ |
Flux de données inter-frontières en un seul appel (Java Caller ➔ JNI Bridge ➔ C++ ARM64 décompilé) | ❌ | ✅ |
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
RegisterNativesExécutez jnisleuth scan sur un APK dont les bibliothèques natives ont été dépouillées :
$ 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.gpr
Avant 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;
}
Après 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;
}
JNISleuth-MCP est 100 % agnostique au modèle. Vous pouvez le connecter à n'importe quel LLM cloud ou local via le protocole standard Model Context Protocol (stdio JSON-RPC 2.0) ou exporter des schémas natifs d'appel de fonctions OpenAI / Gemini / Ollama :
jnisleuth llm-tools --format openai|gemini|mcp) pour LangChain, LlamaIndex, LiteLLM, OpenAI SDK ou google-genai.