Instrumenting JVM Programs With Frida

- (2 min read)

Frida is very commonly used to instrument Android applications written in Java and compiled to Dalvik bytecode. It is a less well known fact that Frida gained support for instrumenting Java programs running on the HotSpot JVM in a recent version which should work on most JVM versions running on Linux and macOS.

Attempting to instrument a program using the default JVM shipped with most Linux distributions should result in the following error:

[Local::PID::8297]-> Java.available
Error: Java API only partially available; please file a bug. Missing: _ZN6Method4sizeEb, _ZN6Method19set_native_functionEPhb, _ZN6Method21clear_native_functionEv, _ZN6Method24restore_unshareable_infoEP6Thread, _ZN6Method10jmethod_idEv, _ZN20ClassLoaderDataGraph10classes_doEP12KlassClosure, _ZN10JavaThread27thread_from_jni_environmentEP7JNIEnv_, _ZN8VMThread7executeEP12VM_Operation, _ZN11OopMapCache22flush_obsolete_entriesEv, _ZN14NMethodSweeper16sweep_code_cacheEv, _ZTV18VM_RedefineClasses, _ZN18VM_RedefineClasses4doitEv, _ZN18VM_RedefineClasses13doit_prologueEv, _ZN18VM_RedefineClasses13doit_epilogueEv, _ZNK18VM_RedefineClasses26allow_nested_vm_operationsEv, _ZN19Abstract_VM_Version19jre_release_versionEv, _ZN14NMethodSweeper11_traversalsE, _ZN14NMethodSweeper13_should_sweepE
    at P (frida/node_modules/frida-java-bridge/lib/jvm.js:143)
    at C (frida/node_modules/frida-java-bridge/lib/jvm.js:12)
    at _tryInitialize (frida/node_modules/frida-java-bridge/index.js:17)
    at v (frida/node_modules/frida-java-bridge/index.js:9)
    at <anonymous> (frida/node_modules/frida-java-bridge/index.js:312)
    at call (native)
    at o (/_java.js)
    at <anonymous> (/_java.js)
    at <anonymous> (frida/runtime/java.js:1)
    at call (native)
    at o (/_java.js)
    at r (/_java.js)
    at <eval> (frida/runtime/java.js:3)
    at _loadJava (native)
    at get (frida/runtime/core.js:114)
    at <anonymous> (<input>:22)

This is because Frida requires the JVM contain symbols in order to locate specific functions required for instrumentation. The default JVM shipped with most Linux distributions have symbols stripped.

$ nm /usr/lib/jvm/java-1.11.0-openjdk-amd64/lib/server/libjvm.so
nm: /usr/lib/jvm/java-1.11.0-openjdk-amd64/lib/server/libjvm.so: no symbols

The easiest way to obtain a JVM with symbols is by downloading a pre-built binary from the AdoptOpenJDK project. By configuring the target program to use a JVM with symbols, Frida can then be used for instrumentation.

     ____
    / _  |   Frida 14.2.3 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://www.frida.re/docs/home/

[Local::PID::10925]-> Java.available
true

The full power of Frida can then be leveraged for reverse engineering and analysis of Java programs.