Java agent to unfinalize class
That one boring Saturday I wanted to learn something more about agents and thought it would be a really cool idea to “unfinalize” java.lang.String
.
I started working on the project, developed simple transformer:
if (name.equals("java/lang/String")) {
ClassPool classPool = ClassPool.getDefault();
final CtClass ctClass;
try {
ctClass = classPool.get(name.replace("/", "."));
} catch (NotFoundException e) {
throw new RuntimeException(e);
}
int modifiers = ctClass.getModifiers();
if (Modifier.isFinal(modifiers)) {
System.out.println(name + " modifiers: " + modifiers);
ctClass.setModifiers(ctClass.getModifiers() & -Modifier.FINAL);
try {
System.out.println("removed modifiers");
return ctClass.toBytecode();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
return b;
and it quickly turned out that java.lang
package isn’t passed to ClassFileTransformer
used in Java agents. When I scan for java.lang.String
, it doesn’t appear, so I won’t be able to modify it. I found another victim that’s a final class java/nio/channels/Channels
, so I’ll go with it for now.
I got the agent working, so I developed a victim Main class where I can test it with java -javaagent
. The class only prints Class modifiers, so I can test that the agent changes binary values:
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("java/nio/channels/Channels".replace("/", "."));
final int modifiers = clazz.getModifiers();
System.out.println("Happy days for modifiers: " + modifiers);
}
}
Now I can package the agent and run Main class using:
mvn package && \
java -javaagent:./target/java-agent-to-remove-class-modifiers-1.0-SNAPSHOT.jar Main.java
Which results with the following output:
java/nio/channels/Channels modifiers: 17
remove modifiers
Exception in thread "main" java.lang.IllegalAccessError: failed to access class java.nio.channels.Channels from class java.nio.file.spi.FileSystemProvider (java.nio.channels.Channels and java.nio.file.spi.FileSystemProvider are in module java.base of loader 'bootstrap')
at java.base/java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:426)
at java.base/java.nio.file.Files.newInputStream(Files.java:160)
at jdk.compiler/com.sun.tools.javac.launcher.Main.readFile(Main.java:235)
at jdk.compiler/com.sun.tools.javac.launcher.Main.compile(Main.java:371)
at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:189)
at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132)
Turns out, final flag cannot be removed from a class in Java 17, neither in 11, but when I split the run instruction to Java 8 compatible format (no single runnable classes):
sdk use java 8.0.265-open
javac Main.java
java -javaagent:./target/java-agent-to-remove-class-modifiers-1.0-SNAPSHOT.jar Main
java/nio/channels/Channels modifiers: 17
remove modifiers
Happy days for modifiers: 16
It was possible to modify the final flag of a class in Java 8 :)