Skip to content

ryad313/jni-function-table-inspector

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JNI Function Table Inspector

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.

Why This Exists

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.

Use Cases

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?

How It Works

                     ┌─────────────────────────────────────┐
                     │         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         │
                     └─────────────────────────────────────┘

Detection Method

  1. Locate jvm.dllGetModuleInformation → base address + size = valid range
  2. Attach to JVMJNI_GetCreatedJavaVMsAttachCurrentThreadAsDaemonJNIEnv*
  3. Read function tableJNIEnv* is a JNINativeInterface_** → dereference twice → 233 function pointers
  4. Classify each pointerGetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS) → owning module
  5. Compare — if owning module ≠ jvm.dllHOOK DETECTED
  6. Report — JSON output with every function's address, module, and hook status
  7. Self-unloadFreeLibraryAndExitThread → zero persistent footprint

Entire lifecycle: ~2 seconds. No hooks installed, no state modified, no threads left behind.

Build

Prerequisites

  • MSVC (Visual Studio 2019+ or Build Tools)
  • JDK 8+ with JAVA_HOME set (needs jni.h)

Option A: build.bat (simplest)

:: Run from Visual Studio Developer Command Prompt
set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_251
build.bat

Option B: CMake

mkdir build && cd build
cmake .. -DJAVA_HOME="C:/Program Files/Java/jdk1.8.0_251"
cmake --build . --config Release

Output

jni_inspector.dll — inject this into any running JVM process.

Usage

  1. Start any Java application (Minecraft, IntelliJ, Tomcat, your own app)
  2. Inject jni_inspector.dll using your preferred method:
    • Process Hacker → right-click process → Miscellaneous → Inject DLL
    • Any DLL injector that supports LoadLibraryA-based injection
  3. Read the report at %TEMP%\jni_inspector\report.json

The DLL automatically detaches and unloads itself after writing the report.

Output Format

report.json

{
  "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"}
  ]
}

inspector.log

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.

JNI Function Table Reference

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.

Technical Details

Why this approach works

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.

Limitations

  • JVMTI-based interception that uses SetEventCallbacks is 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 FindClass inside 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 JNIEnv tables. This tool inspects the table from its own (newly attached) thread

Platform

  • Windows x64 (uses GetModuleHandleEx, MODULEINFO, etc.)
  • JDK 8-21 tested. The JNI table is append-only by spec — all indices 0-232 are stable.

Legal

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.

License

MIT — see LICENSE.

About

Detects runtime modifications to the JNI function table (JNINativeInterface_) in a running JVM. Inject DLL ? scan 233 function pointers ? identify hooks ? JSON report.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages