#JvmUtils - Extensible Jvm internals access

1 messages · Page 1 of 1 (latest)

elder yarrow
#

This is a project I wrote that bypasses Jigsaw in order to achieve opening up all jvm modules and allow for direct access to jvm internals such as the JITCompiler, JDI, JVMTI, direct off-heap memory management, and more.

This requires no jvm flags, no special builds. The bypass does work up through jdk 24, however the api is targetted to jdk 11 internals.

To bypass Jigsaw, we reflect Unsafe and read IMPL_LOOKUP directly from memory using Unsafe::staticFieldBase()/Unsafe::staticFieldOffset() to get its address, then Unsafe::getObject() to read it, avoiding reflection's module access checks. Since Unsafe operates at memory level below module enforcement, it retrieves the privileged IMPL_LOOKUP (which has universal access) from the non-exported java.lang.invoke package that reflection cannot access. And from there, we can use IMPL_LOOKUP (kind of the JVMs master key) to access Module.class and call Module::addOpensToAllUnnamed() on all jvm modules to open up unfettered access to everything.

API Layers Overview

High-Level APIs
├── DirectMemoryManager     (Memory Management)
├── JITCompilerAccess       (Compilation Control)
├── BytecodeAPI            (Code Generation used internally to support reflection)
└── JDI Integration        (Debugging)

Mid-Level APIs  
├── WhiteBox               (JVM Testing Interface)
├── SharedSecrets          (Internal Communications)
├── ThreadTracker          (Thread Monitoring)
└── VM Control             (VM Lifecycle)

Low-Level Foundation
├── InternalUnsafe         (Memory Operations)
├── ReflectBuilder         (Enhanced Reflection)
└── Utility Classes        (Support Functions)

https://github.com/Tonic-Box/JvmUtils

GitHub

Extensible utility classes for everything you've ever wanted to do in the JVM. - Tonic-Box/JvmUtils

grim steeple
elder yarrow
#

Not really a good or a bad thing. Just opens up and enables some neat things that are incredibly useful for some more niche things. I mean many large libraries have to bypass jigsaw to a degree (the main reason they've never truely hardened it) such as guice, guava, springboot, RxJava, lombok, and more.

#

This is just a slightly different and more universal approach to sidestepping it.

#

With some poc api exposing internal tooling of the jvm

grim steeple
elder yarrow
#

Also, find me a 2ndary way to expose programmatic control of jit behavior, I'll wait lol

#

Even take a library like netty for example. Does nearly the same thing I do here for off heap memory access for it's byte buffer stuff as an optimization. Also requires side stepping jigsaw

#

Again, it's intentionally not been hardened due to so much of the eco system currently for java being so reliant of being able to bypass it.

#

I'd love and accept critique on approach, design, code style, and so on. But simply offering "I think this was a mistake" as a critique, especially when so much of the more intrinsic java ecosystem is at complete odds with your position here, isn't really useful. Respectfully.

#

Jigsaw truely was a solution no one asked for to an issue no one had lol

tribal sparrow
#

I'm happy to see a broad approach to bypassing unneeded encapsulation on a case by case basis. Every tool has its uses.

Does this library only expose access to modules of the JVM that were not exported or does it also wrap things up to an additional API? What would be the difference between using this and compiling your own JVM with additional exports?

finite mason
#

"here is a big file with all the add opens" is basically the same thing

grim steeple
grim steeple
grim steeple
elder yarrow
#

That's fine, I still enjoyed writing this. If it's an approach that's good enough for googles engineers, then it's good enough for me. 🙂

elder yarrow
# finite mason Why not just pass the flags?

Why? A drive to understand how things work on a deeper level. My frustration here is that the entire point of this as being just a neat technical POC is completely missed. Lol

elder yarrow
#

Opens some fantastic doors in terms of runtime analysis and reverse engineering on the jvm. Opens nearly every door that ssvm was written as a way to open.

finite mason
#

call Module::addOpensToAllUnnamed() on all jvm modules to open up unfettered access to everything.

#

so all you need to do is programatically get a list of all modules and packages

#

then make a big file with

#
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
#

and then just run java @openeverything.txt <rest of args>

#

argfiles are great for that

#

which really doesn't seem that bad. You distribute the argfile and then people using your dark magic library stuff just need to add that when they run

elder yarrow
#

Ah fair, this is more a demonstration of the approach just as much though.

#

Honestly a lot of this stuff should be patched into on a more strict level outside of jigsaw. For example, direct access to jvmti makes the disable attatch mechanism arg fully useless

finite mason
#

Yeah - but I think you got a sizable collection of fart magic here. Might as well be ready for when that hole is patched

#

Sorry dark magic

#

No not sorry

#

Fart magic

elder yarrow
#

Yeah that's more the point, not a production lib, just technically interestinh pocs

finite mason
#

And that's why it's annoyig

#

Because if you change a private field on forkjoinpool you can break virtual threads and get miscompliations and God knows what else now

elder yarrow
#

It's a joke though, and purposefully not locked does, there's several other non unsafe/impl_lookup ways to bypass it

#

Which is why I can't believe it's in any sense meant as a true security feature

finite mason
#

The plan is to shut it all down

elder yarrow
finite mason
#

Default?

elder yarrow
#

*segfault

#

But I get the point tho

#

Just being a bit pedantic xd

elder yarrow
#

And are they planning on doing this for lts versions like 11 and 17? Or just 25+ etc?

#

Also this doesn't target oracle, it targets hotspot jvm impls

finite mason
#

And distribute it literally like any other language that runs on the JVM - with the position that it is not Java, it's a different language

finite mason
#

It doesn't affect spring boot as far as I know

elder yarrow
elder yarrow
# finite mason It doesn't affect spring boot as far as I know

Okay yeah it does for the following core mechanisms:

Dependency Injection: Accessing private fields and constructorsAuto-configuration: Dynamically creating beans based on classpath scanningProperty binding: Mapping configuration properties to objectsAOP proxies: Creating dynamic proxies that need deep access
finite mason
#

just your code

#

which is different

elder yarrow
#

Okay yeah dug through and none of it does anything beyond normal reflection.

west crypt
#

While I 100% agree that you shouldn't write any code that relies on any mechanism in this library, there's some cool stuff in there

#

I just wonder how much of this is still relevant if you target 24 or 25

#

Like, bytecodeapi we have now as the classfile api

#

Directmemory are obviously memorysegments

#

etc

#

I do like it as a collection of "shit you shouldn't do" 😄

#

I knew of some of these, some other ones are new to me

elder yarrow
elder yarrow
#

Ik up to 24 the bypass itself works

#

Not sure in what all internals are still the same tho

west crypt
#

Yeah, just upgrade lol 😄

elder yarrow
west crypt
#

oof, that sucks

#

MC?

elder yarrow
#

Osrs

west crypt
#

ah

elder yarrow
#

Yee

elder yarrow
# west crypt oof, that sucks

It's rly not so bad, the actual gsmepack is 6, for injecting I transform it to 11 though. Being 6 made writing my remapper a lot simpler than it would have been if indys were in the mix too, so it's really not so bad

west crypt
#

😄

#

All of that sounds so cursed

elder yarrow
#

Took kind of a unique approach to remapping too, I do iterative refinement of mappings using tf-idf + call graphs

elder yarrow
#

Remapping as in when new revision comes out,matching everything in old jar to it's equivalent in the new jar since everything is shuffeled/renamed, reobfuscated and so on

elder yarrow
#

My mixin system supports clojre for example lol

finite mason
elder yarrow
# finite mason how?

Wdym? You can call stuff between the 2. Clojre support is only for specific pipelines that inject calls to stuff, the more fine grained mixin pipelines are only supported in Java though. Added mostly as a meme for a friend who has an unhealthy obsession with clojrescript lol

finite mason
#

i thought you were just misspelling clojure

#

oh you were

#

like i thought mixins were done using annotated class definitions that get shredded apart later

#

i don't know how you would specify that stuff in clojure

#

unless you made some wacky ass macros

elder yarrow
#

Kinda, just through functional interfaces, it's very wacky and not very useful lol

Closure still better than kotlin tho 🥲

#

I mean, Clojure.var() and all that

finite mason
#

yeah i know how to do clojure interop

#

i might not fully understand mixins i guess

elder yarrow
#

Mixins are abstract classes with class annotation that specify target class in ganepack, then fields/methods in that class with annotations that define what they're fore eg methodhook, field hook, shadow, and so on. Basically just fancy instructions for the injector that takes them and applies them t o the gamepack classes (different bytecode transformations)

elder yarrow
# finite mason i might not fully understand mixins i guess

Sample mixin class might look like this:

package com.tonic.mixins;

import com.tonic.Logger;
import com.tonic.api.*;
import com.tonic.injector.annotations.*;
import com.tonic.injector.util.ExceptionUtil;
import com.tonic.model.ui.VitaLiteOptionsPanel;

@Mixin("Client")
public abstract class TClientMixin implements TClient
{
    @Shadow("packetWriter")
    private static TPacketWriter packetWriter;

    @Shadow("MouseHandler_instance")
    private static TMouseHandler mouseHandler;

    @Inject
    @Override
    public TPacketWriter getPacketWriter()
    {
        return packetWriter;
    }

    @Inject
    @Override
    public TMouseHandler getMouseHandler()
    {
        return mouseHandler;
    }

    @Override
    @Construct("ClientPacket")
    public abstract TClientPacket newClientPacket(int id, int length);

    @Shadow("getPacketBufferNode")
    public abstract TPacketBufferNode getPacketBufferNode(TClientPacket clientPacket, TIsaacCipher isaacCipher);

    @Shadow("mouseLastPressedTimeMillis")
    private static long clientMouseLastPressedMillis;

    @Inject
    public long getClientMouseLastPressedMillis() {
        return clientMouseLastPressedMillis;
    }

    @Inject
    public void setClientMouseLastPressedMillis(long millis) {
        clientMouseLastPressedMillis = millis;
    }

    @FieldHook("MouseHandler_idleCycles")
    public static boolean onIdleCycleSet(int value) {
        return false;
    }
}
finite mason
#

though i don't understand that either

#

like how exactly is this shredded apart

#

and my confusion with clojure was just it felt like harder bytecode to shred

elder yarrow
#

Like are you asking how the injector works? How the bytecodevtransformers work? Or?

#

Gamepack injection happens in memory prior to the classes being loaded

#

All the mixins are scanned from their package classpath, then integrated over checking annotations and throwing them down the correct transformer pipelines modifying the gamepack