Fogetti

← back to the blog


Changing a private Java constructor with ASM

Posted on July 1st, 2016 in java, instrumentation, constructor, ASM

In this blog post I will introduce a nifty way of modifying the access modifier of the constructor of a Java class.

Let's suppose that you have a class with a private constructor

public class Private<T> {

    private T test;

    private Private(){
    }

    @Override
    public String toString() {
        return "Private [test=" + test + "]";
    }

}

Now let's also suppose that you have to instantiate this class from your client. What are your options?

Well if you have access to the code then you can just go and modify the access modifier and change it to protected, public, etc. Or you can also use reflection if your JVM's security manager allows you to do so.

But there is one more nifty way of doing it. Using Java instrumentation and ASM. The Java instrumentation API basically allows custom code to modify the loaded classes before or during the rest of the JVM is using those classes. And ASM just like reflection provides reflection like capabilities to read a class in it's serialized byte form and manipulate those classes dynamically.

Let's see how to do this. Let's create a transformer first:

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

public class Transformer {

    private static Instrumentation instrumentation;

    public static void premain(String agentArguments, Instrumentation instrumentation) {
        Transformer.instrumentation = instrumentation;
        instrumentation.addTransformer(new ClassFileTransformer() {

            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
                throws IllegalClassFormatException {
                if (className.equals("Private")) {
                    try {
                        ClassReader cr = new ClassReader(classfileBuffer);
                        ClassNode cn = new ClassNode();
                        cr.accept(cn, 0);

                        for (Object methodInst : cn.methods) {
                            MethodNode method = (MethodNode) methodInst;
                            if (method.name.equals("<init>") && method.desc.equals("()V")) {
                                System.out.println(String.format("Transforming: [%s|%s]",method.name, method.desc));
                                method.access &= ~Opcodes.ACC_PRIVATE;
                                method.access |= Opcodes.ACC_PUBLIC;
                                System.out.println(String.format("Access flags: [%s]", Integer.toBinaryString(method.access)));
                            }
                        }
                        ClassWriter result = new ClassWriter(0);
                        cn.accept(result);
                        System.out.println("Transforming DONE");
                        return result.toByteArray();
                    } catch (Throwable e) {
                        e.printStackTrace();
                        return null;
                    }
                }
                return null;
            }
        }, true);
    }

    public static void retransformClasses(Class<?>... classes) throws UnmodifiableClassException {
        instrumentation.retransformClasses(classes);
    }

}

In this example we take a class called Private in it's byte form. We load this class with the ClassReader, then we check if any of the methods has a private constructor signature, and if it does then we change the constructor to public. When we are done, we are returning the class in it's byte form. Simple enough.

Then we only have to do one more thing. Use the private class in our client code:

public class Client {

    public static void main(String[] args) {
        Client client = new Client();
        client.load();
    }

    @SuppressWarnings({ "unchecked" })
    private void load() {
        try {
            Class<?> clazz = Class.forName("Private");
            Private<String> priv = (Private<String>) clazz.newInstance();
            System.out.println(String.format("Behold! [%s]", priv.toString()));
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

To make this client work, you will have to call the java command with an additional -javaagent: argument, like this:

java -javaagent:/your/dir/jar-with-dependencies.jar Client

And how to build this jar? Well if you use maven you can make your POM look like this:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>sample</groupId>
    <artifactId>sample</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sample</name>
    <description>sample</description>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <index>true</index>
                        <manifest>
                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
                        </manifest>
                        <manifestEntries>
                            <Premain-Class>Transformer</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                            <Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
                        </manifestEntries>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <!-- this is used for inheritance merges -->
                        <phase>package</phase>
                        <!-- append to the packaging phase. -->
                        <goals>
                            <goal>single</goal>
                            <!-- goals == mojos -->
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-all</artifactId>
            <version>5.1</version>
        </dependency>
    </dependencies>
</project>