Introduction
Starting from JDK9, the Java Platform Module System (JPMS) was introduced, for detailed introduction, you can refer to Oracle's official description of the new features of JDK9: https://docs.oracle.com/javase/9/whatsnew/toc.htm
About the access permissions between modules:

The access permissions of Java's class are usually divided into: public, protected, private, and default package access permissions. After JDK9 introduced the concept of modules, these concepts need to be distinguished from modules. The access permissions of these classes have not been invalidated, but they can only take effect within the module. If modules want to access our classes externally, they need to be explicitly exported, that is, usingexports
module hello.world {
exports com.itranswarp.sample;
requires java.base;
requires java.xml;
}
demo as follows:
import javax.management.loading.MLet;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Base64;
public class Main {
public static void main(String[] args) {
try {
String evilClassBase64 = "xxxx";
byte[] bytes = Base64.getDecoder().decode(evilClassBase64);
Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
method.setAccessible(true);
Class cc = (Class) method.invoke(new MLet(new URL[0], Main.class.getClassLoader()), bytes, new Integer(0), new Integer(bytes.length));
cc.newInstance();
}
}
}
}
Run the above demo separately onJDK11
andJDK21
test in the environment
JDK 11
After running the above demo, it will prompt illegal reflection operation, and it will prompt that such unsafe reflection operations will be completely disabled in future versions, but it will not affect bytecode loading
JDK21
After JDK17, strong encapsulation directly bans illegal reflection, and you can see from the error message that the java.base module's java.lang package does not open reflection to the unnamed module
Explanation given by the Oracle official document
https://docs.oracle.com/en/java/javase/17/migrate/migrating-jdk-8-later-jdk-releases.html#GUID-7BB28E4D-99B3-4078-BDC4-FC24180CE82B
Here we need to look at a module instruction after JDK9
open, opens, opens…to instructions
Before Java 9, we could use reflection technology to obtain information about all classes and their internal members under a certain package, even for private types, so class information is not really completely isolated from the outside. One of the main goals of the module system is to achieve strong encapsulation. By default, unless a class is explicitly exported or declared as public type, the classes in the module are not visible to the outside. Modularization requires us to expose the scope of packages to external modules to the minimum extent. The instructions related to open are used to limit which classes can be detected by reflection technology at runtime.
Let's first look at the opens instruction, the syntax is as follows:
opens package
The opens instruction is used to specify that all public classes under a certain package can only be reflected by other modules at runtime, and all classes and their members under the package can be accessed through reflection.
The syntax of the opens…to instruction is as follows:
opens package to modules
This instruction is used to specify that certain modules can perform reflection operations on public classes under specific packages of the module at runtime, followed by module names separated by commas after 'to'.
The open instruction syntax is as follows:
open module moduleName{
}
The instruction is used to specify that external modules can perform reflection operations on all classes under this module at runtime.
That is to say, JDK17+ did not include the instructions we need when developingjava.lang
Open reflection permissions, causing us to be unable to perform reflection class loading, check the source code in the JDKmodule-info.class
definition, I found that indeed it is not usedopen
instruction
Afterwards, I found in Oracle's official documentation that the official reservedsun.misc
andsun.reflect
Two packages can perform reflection calls
Afterwards, I looked at the source code of JDK21 and found in the JDKjdk.unsupported
modulemodule-info
There is a declaration
Using the opens instruction, classes under the two package names can be reflected.
Unsafe
For an introduction to the Unsafe class, see this article: https://javaguide.cn/java/basis/unsafe.html
The article mentioned that it hasdefineClass
anddefineAnonymousClass
Two methods can load bytecode in two ways. However, the author found in the actual JDK8, JDK11, and JDK21 environments that there are two methods in JDK8, but onlydefineAnonymousClass
One method, even both methods of JDK21 have been removed......
Below are three versions of JDK'sUnsafe
class
JDK8
JDK11
JDK21
Afterwards, I found through searching related articles that JDK 17 has removeddefineAnonymousClass
method.
That is to say, before <JDK17, you can directly usedefineAnonymousClass
This method is used for reflection class loading operations.
Field field = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
unsafe.defineAnonymousClass(Class.class, bytes, null).newInstance();
JDK17+ bytecode loading
Although JDK modularization officially reservedsun.misc
andsun.reflect
Two packages can perform reflection calls, but JDK17+ also removedUnsafe
ofdefineAnonymousClass
method. This causes the previous loading method to fail.
Afterwards, I saw @Aiwin master share through modifying the current class's module tojava.base
Maintain andjava.lang.ClassLoader
Under the same module, it can break the limitations of modularization, thus enabling the loading of bytecode files.
private boolean checkCanSetAccessible(Class<?> caller,
Class<?> declaringClass,
boolean throwExceptionIfDenied) {
if (caller == MethodHandle.class) {
throw new IllegalCallerException(); // should not happen
}
Module callerModule = caller.getModule();
Module declaringModule = declaringClass.getModule();
if (callerModule == declaringModule) return true;
if (callerModule == Object.class.getModule()) return true;
if (!declaringModule.isNamed()) return true;
String pn = declaringClass.getPackageName();
int modifiers;
if (this instanceof Executable) {
modifiers = ((Executable) this).getModifiers();
} else {
modifiers = ((Field) this).getModifiers();
}
// class is public and package is exported to caller
boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers());
if (isClassPublic && declaringModule.isExported(pn, callerModule)) {
// member is public
if (Modifier.isPublic(modifiers)) {
logIfExportedForIllegalAccess(caller, declaringClass);
return true;
}
// member is protected-static
if (Modifier.isProtected(modifiers))
&& Modifier.isStatic(modifiers)}
&& isSubclassOf(caller, declaringClass)) {
logIfExportedForIllegalAccess(caller, declaringClass);
return true;
}
}
// package is open to caller
if (declaringModule.isOpen(pn, callerModule)) {
logIfOpenedForIllegalAccess(caller, declaringClass);
return true;
}
if (throwExceptionIfDenied) {
// not accessible
String msg = "Unable to make ";
if (this instanceof Field)
msg += "field ";
msg += this + " accessible: " + declaringModule + " does not \""
if (isClassPublic && Modifier.isPublic(modifiers))
msg += "exports";
else
msg += "opens";
msg += " " + pn + "\" to " + callerModule;
InaccessibleObjectException e = new InaccessibleObjectException(msg);
if (printStackTraceWhenAccessFails()) {
e.printStackTrace(System.err);
}
throw e;
}
return false;
}
From the above method, it can be seen that it mainly checks the class's module. The Unsafe class can modify the offset, and by modifying our class's module to the base module, we can bypass the reflection restrictions of JDK17+ versions.
String evilClassBase64 = "xxxx";
byte[] bytes = Base64.getDecoder().decode(evilClassBase64);
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Module baseModule = Object.class.getModule();
Class currentClass = Main.class;
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.putObject(currentClass, offset, baseModule);
Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
method.setAccessible(true);
((Class)method.invoke(ClassLoader.getSystemClassLoader(), bytes, 0, bytes.length)).newInstance();

评论已关闭