A lightweight C++ DLL that detects runtime modifications to the JNI function table (JNINativeInterface_) in a running Java Virtual Machine. Inject it into any JVM process to instantly map which JNI functions have been intercepted — and by which module.
Every JNI call — FindClass, GetFieldID, CallObjectMethod, RegisterNatives — is dispatched through a struct of 233 function pointers called JNINativeInterface_. This struct is what JNIEnv* actually points to.
Security agents, instrumentation frameworks, and APM tools modify this table at runtime to intercept JNI calls. The indices are standardized by the JNI specification and identical across all JDK versions — making the table a stable, well-defined attack surface.
This tool makes those modifications visible.
| Scenario | What you learn |
|---|---|
| JVM security audit | Which JNI functions does a JVMTI agent intercept? |
| Agent debugging | Why does GetFieldID return invalid data when my agent is loaded? |
| APM analysis | What's the interception surface of New Relic / Dynatrace / Datadog? |
| Forensics | Has an unauthorized module modified the JNI dispatch table? |
┌─────────────────────────────────────┐
│ JVM Process (javaw.exe) │
│ │
┌──────────┐ │ JNIEnv* ──► JNINativeInterface_ │
│ Inject │──DLL──│ ┌───────────────┐ │
│ DLL │ │ │ [4] GetVersion│──►│ jvm.dll ✓
└──────────┘ │ │ [6] FindClass │──►│ agent.dll ✗ HOOK!
│ │ [33] GetMethod │──►│ jvm.dll ✓
│ │ [94] GetFieldID│──►│ agent.dll ✗ HOOK!
│ │ ... │ │
│ └───────────────┘ │
│ │
│ Output: %TEMP%\jni_inspector\ │
│ ├── report.json │
│ └── inspector.log │
└─────────────────────────────────────┘
- Locate
jvm.dll—GetModuleInformation→ base address + size = valid range - Attach to JVM —
JNI_GetCreatedJavaVMs→AttachCurrentThreadAsDaemon→JNIEnv* - Read function table —
JNIEnv*is aJNINativeInterface_**→ dereference twice → 233 function pointers - Classify each pointer —
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS)→ owning module - Compare — if owning module ≠
jvm.dll→ HOOK DETECTED - Report — JSON output with every function's address, module, and hook status
- Self-unload —
FreeLibraryAndExitThread→ zero persistent footprint
Entire lifecycle: ~2 seconds. No hooks installed, no state modified, no threads left behind.
- MSVC (Visual Studio 2019+ or Build Tools)
- JDK 8+ with
JAVA_HOMEset (needsjni.h)
:: Run from Visual Studio Developer Command Prompt
set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_251
build.batmkdir build && cd build
cmake .. -DJAVA_HOME="C:/Program Files/Java/jdk1.8.0_251"
cmake --build . --config Releasejni_inspector.dll — inject this into any running JVM process.
- Start any Java application (Minecraft, IntelliJ, Tomcat, your own app)
- Inject
jni_inspector.dllusing your preferred method:- Process Hacker → right-click process → Miscellaneous → Inject DLL
- Any DLL injector that supports
LoadLibraryA-based injection
- Read the report at
%TEMP%\jni_inspector\report.json
The DLL automatically detaches and unloads itself after writing the report.
{
"version": "1.0",
"timestamp": "2026-06-29T04:00:00",
"jvm_module": "C:\\Program Files\\Java\\jdk1.8.0_251\\jre\\bin\\server\\jvm.dll",
"jvm_base": "0x7FFA12340000",
"jvm_size_bytes": 10485760,
"total_inspected": 229,
"hooks_detected": 3,
"functions": [
{"index": 4, "name": "GetVersion", "address": "0x7FFA12345678", "module": "jvm.dll", "hooked": false},
{"index": 6, "name": "FindClass", "address": "0x7FFB00001234", "module": "agent.dll", "hooked": true},
...
],
"hooked_functions": [
{"index": 6, "name": "FindClass", "redirected_to": "agent.dll"},
{"index": 94, "name": "GetFieldID", "redirected_to": "agent.dll"},
{"index": 144, "name": "GetStaticFieldID", "redirected_to": "agent.dll"}
]
}Human-readable timestamped log:
[04:00:00.123] ═══ JNI Function Table Inspector v1.0 ═══
[04:00:00.124] jvm.dll: C:\Program Files\Java\jdk1.8.0_251\jre\bin\server\jvm.dll
[04:00:00.124] range: [0x7FFA12340000 — 0x7FFA12D40000) (10240 KB)
[04:00:00.125] JVM attached (daemon thread)
[04:00:00.126] [HOOK] #6 FindClass → 0x7FFB00001234 (agent.dll)
[04:00:00.126] [HOOK] #94 GetFieldID → 0x7FFB00005678 (agent.dll)
[04:00:00.126] [HOOK] #144 GetStaticFieldID → 0x7FFB00009ABC (agent.dll)
[04:00:00.127] Scan complete: 229/233 functions inspected, 3 hooks detected
[04:00:00.128] Report: C:\Users\...\AppData\Local\Temp\jni_inspector\report.json
[04:00:00.629] Detached. Self-unloading.
Key indices that are commonly intercepted by security agents:
| Index | Function | Why it's targeted |
|---|---|---|
| 5 | DefineClass |
Block runtime class injection |
| 6 | FindClass |
Control class resolution / return spoofed classes |
| 33 | GetMethodID |
Block access to specific methods |
| 94 | GetFieldID |
Block access to specific fields |
| 113 | GetStaticMethodID |
Block static method access |
| 144 | GetStaticFieldID |
Block static field access |
| 215 | RegisterNatives |
Monitor native method registration |
The complete mapping of all 233 indices is in src/inspector.cpp.
The JNINativeInterface_ struct is defined in jni.h and lives in heap memory allocated by the JVM. In an unmodified JVM, every function pointer in this struct resolves to an address within jvm.dll. When a JVMTI agent or security tool wants to intercept JNI calls, it replaces these pointers with its own function addresses.
This is the standard interception mechanism — the JNI specification explicitly supports it via JNI_GetCreatedJavaVMs and the SetJNIFunctionTable JVMTI extension. What this tool does is simply read the table and check where each pointer leads.
- JVMTI-based interception that uses
SetEventCallbacksis NOT detected — this tool only inspects the function pointer table, not JVMTI event hooks - Inline hooks within jvm.dll functions are NOT detected — if an agent patches the first bytes of
FindClassinside jvm.dll rather than redirecting the table pointer, the pointer still resolves to jvm.dll - Per-thread JNI environments — some agents may only modify specific thread's
JNIEnvtables. This tool inspects the table from its own (newly attached) thread
- Windows x64 (uses
GetModuleHandleEx,MODULEINFO, etc.) - JDK 8-21 tested. The JNI table is append-only by spec — all indices 0-232 are stable.
This tool is intended for security research, debugging, and educational purposes in controlled environments. It reads JVM state in a non-destructive, read-only manner and does not modify any process memory.
Do not deploy against systems you do not own or have explicit authorization to test.
MIT — see LICENSE.