JVMLowLevel
JVMLowLevel copied to clipboard
JVM 底层技术/JVM沙箱技术
JVMLowLevel
目标
- [ ] 反射重定向 | Redirect Reflection
- [X] JDK 8
- [ ] JDK 13
- [ ] MethodHandles 越权拦截 | Blocking MethodHandles
- [X] JDK 8
- [ ] JDK 13
- [ ] Unsafe 拦截 | Blocking Unsafe
- [ ] MagicAccessorImpl 拦截 | Blocking MagicAccessorImpl
- [X] JDK 8
- [ ] JDK 13
Libraries used
- ASM 7.2
- Kotlin Java Runtime
Realization
Pass Instrumentation
to the main thread
Get Unsafe
instance
instrumentation = Thread.currentThread().threadGroup.let {
val count = it.activeCount()
val array = Array<Thread?>(count) { null }
it.enumerate(array)
return@let array
}.let {
it.forEach { thread ->
if (thread == null) return@forEach
if (thread.name == "InstrumentationThread") {
if (thread is Supplier<*>) {
return@let thread.get() as Instrumentation
}
}
}
error("Instrumentation not found! Please add -javaagent:JvmAgent.jar")
}
unsafe = Unsafe::class.java.getDeclaredField("theUnsafe").also { it.isAccessible = true }.get(null) as Unsafe
unsafe.ensureClassInitialized(ClassNode::class.java)
Redirect Reflection(JDK 8)
Make a model of sun.reflect.ReflectionFactory
, like
ReflectionFactoryModel
Then make a class to extend it. like
// Don't use object, it is not support
class MyCustomReflectionFactory private constructor() : ReflectionFactoryModel() {
}
So, we make a custom factory. It extends ReflectionFactoryModel
,
NOT extends sun.reflect.ReflectionFactory
. We need to make the
JVM think that this class extends ReflectionFactory
.
We use Instrumentation
to edit the bytecode.
unsafe.ensureClassInitialized(ClassNode::class.java) // Preload ASM
instrumentation.addTransformer { loader, className, classBeingRedefined, protectionDomain, classfileBuffer ->
ClassReader(classfileBuffer).let { reader ->
ClassNode().also {
reader.accept(it, 0)
}
}.let {
// If the class is the model, ignore it
if (it.name == "io/github/karlatemp/jll/ReflectionFactoryModel")
return@addTransformer classfileBuffer
val writer = ClassWriter(0)
it.accept(
ClassRemapper(
writer, SimpleRemapper(
"io/github/karlatemp/jll/ReflectionFactoryModel",
"sun/reflect/ReflectionFactory"
)
)
)
writer
}.toByteArray()
}
Then we need to alloc it. We CANNOT use new MyCustomReflectionFacotry()
.
Because the constructor of sun.reflet.ReflectionFactory
is private.
We use sun.misc.Unsafe
to alloc our custom reflection factory.
val customReflectionFactory = unsafe.allocateInstance(MyCustomReflectionFactory::class.java) as MyCustomReflectionFactory
if(!sun.reflect.ReflectionFactory::class.isInstance(customReflectionFactory)) {
error("Oops. This is not working!")
}
Override jvm default reflection factory. We search all loaded classes to ensure that there are no missing fields.
instrumentation.allLoadedClasses.forEach {
kotlin.runCatching {
it.declaredFields.forEach { field ->
if (Modifier.isStatic(field.modifiers)) {
if (field.type == ReflectionFactory::class.java) {
unsafe.putObject(unsafe.staticFieldBase(field), unsafe.staticFieldOffset(field), customReflectionFactory)
}
}
}
}
}
Then, the most important step. Clean all the reflection data.
val classReflectionDataOffset = unsafe.objectFieldOffset(Class::class.java.getDeclaredField("reflectionData"))
instrumentation.allLoadedClasses.forEach {
unsafe.putObjectVolatile(it, classReflectionDataOffset, null)
}
We have done these steps, but we have to confirm whether they work or not.
// Don't use object, it is not support
class MyCustomReflectionFactory private constructor() : ReflectionFactoryModel() {
override fun newMethodAccessor(var0: Method): MethodAccessor {
println(var0)
return super.newMethodAccessor(var0)
}
}
Then run
Runnable::class.java.getDeclaredMethod("run").invoke(Runnable {
println("Hello")
})
If there are no surprises, you will see the following log
public abstract void java.lang.Runnable.run()
Hello
LICENSE
The license for this project is GNU AFFERO GENERAL PUBLIC LICENSE version 3