Search code examples
javajvmagentbyte-buddy

Dynamically attaching a Java agent not working


I wrote the following code for a coding challenge. The purpose of the program is to attach to another java process and load an instrumentation engine after the program has been started. This is usually done in Java by implementing an agentmain method in your agent jar file. I generate this all in the code below:

The error is seen on the JVM running the toy program and the clue is it thinks the Agent class file name is Agent$1 for some reason. I comment out my usage of ByteBuddy and just try to load the agent file using the built in libraries and it still fails.

heartbeat : 373 pid = 21992
heartbeat : 374 pid = 21992
Exception in thread "Attach Listener" java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:491)
    at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain(InstrumentationImpl.java:513)
Caused by: java.lang.NoClassDefFoundError: Agent$1
    at Agent.agentmain(Agent.java:8)
    ... 6 more
Caused by: java.lang.ClassNotFoundException: Agent$1
    ... 7 more
Agent failed to start!
import com.sun.tools.attach.VirtualMachine;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.implementation.FixedValue;

import java.io.*;
import java.lang.instrument.Instrumentation;

import static net.bytebuddy.matcher.ElementMatchers.named;

public class Thief {

    public static void main(String[] args) throws Exception {
        Thief thief = new Thief();

        VirtualMachine jvm = VirtualMachine.attach("21992");
        File agentFile = new File("agent.jar");
        jvm.loadAgent(agentFile.getAbsolutePath());

        //thief.guessSecurityCode("24472");
    }

    public String guessSecurityCode(final String pid) throws Exception {
        File jarFile = createAgent();
        ByteBuddyAgent.attach(jarFile, pid);
        return "0000";
    }

    private static String generateSimpleAgent2() {

        return  "import java.lang.instrument.ClassFileTransformer;" + "\n" +
                "import java.lang.instrument.Instrumentation;" + "\n" +
                "import java.security.ProtectionDomain;" + "\n" +
                "\n\n" +
                "public class Agent {" +"\n" +
                "    public static void agentmain(String argument, Instrumentation inst) {" +"\n" +
                "    }" +"\n" +
                "}" +"\n";
    }

    private static String generateSimpleAgent() {

        return  "import java.lang.instrument.ClassFileTransformer;" + "\n" +
                "import java.lang.instrument.Instrumentation;" + "\n" +
                "import java.security.ProtectionDomain;" + "\n" +
                "\n\n" +
                "public class Agent {" +"\n" +
                "    public static void agentmain(String argument, Instrumentation inst) {" +"\n" +
                "        inst.addTransformer(new ClassFileTransformer() {" +"\n" +
                "            @Override" +"\n" +
                "            public byte[] transform(" +"\n" +
                "                ClassLoader loader," +"\n" +
                "                String className," +"\n" +
                "                Class<?> classBeingRedefined," +"\n" +
                "                ProtectionDomain protectionDomain," +"\n" +
                "                byte[] classFileBuffer) {" +"\n" +
                "            System.out.println(\"transform on : \" +className);" +"\n" +
                "            return classFileBuffer;" +"\n" +
                "            }" +"\n" +
                "        });" +"\n" +
                "    }" +"\n" +
                "}" +"\n";
    }

    private static String generateAgentManifest() {
        return  String.join("\n", "Agent-Class: Agent",
                "Can-Retransform-Classes: true",
                "Can-Redefine-Classes: true",
                "Premain-Class: Agent"
        );
    }

    private static String generateAgentManifest2() {
        return  String.join("\n",
                "Manifest-Version: 1.0",
                "Agent-Class: Agent",
                "Permissions: all-permissions"
        );
    }

    private static String generateTransformer() {
        return String.join("\n",
                "import java.lang.instrument.ClassFileTransformer;",
                "import java.security.ProtectionDomain;",
                "import java.util.Arrays;",
                "public class Transformer implements ClassFileTransformer {",
                "    public byte[] transform(ClassLoader loader, String className, Class<?> cls, ProtectionDomain dom, byte[] buf) {",
                "        return null;",
                "    }",
                "}"
        );
    }

    private static void writeFile(String path, String data) throws IOException {
        final PrintWriter out = new PrintWriter(path);
        out.print(data);
        out.close();
    }

    private static void runCommand(String cmd) throws Exception {
        System.out.println("[commmand] " + cmd);
        String s;
        Process p = Runtime.getRuntime().exec(cmd);
        BufferedReader out = new BufferedReader(new InputStreamReader(p.getInputStream()));
        while ((s = out.readLine()) != null) {
            System.out.println("[out] " + s);
        }
        out = new BufferedReader(new InputStreamReader(p.getErrorStream()));
        while ((s = out.readLine()) != null) {
            System.out.println("[err] " + s);
        }
        p.waitFor();
        System.out.println("[exit status] " + p.exitValue());
        p.destroy();
    }

    private static File createAgent() throws Exception {
        writeFile("Agent.java", generateSimpleAgent2());
        writeFile("Transformer.java", generateTransformer());
        writeFile("manifest.mf", generateAgentManifest2());
        runCommand("javac Agent.java Transformer.java");
        runCommand("jar -cfm agent.jar manifest.mf Agent.class Transformer.class");
        return new File("agent.jar");
    }
}

Does anyone know what I'm doing wrong here?


Solution

  • With generateSimpleAgent2(), it works. It does not fail the same way, like you said in your comment under ewramner's answer. That should give you a clue. Furthermore, if you simply read the exception and believe what it is telling you, you know what to do. Again:

    java.lang.ClassNotFoundException: Agent$1
    

    I.e., generateSimpleAgent() creates two class files, one for the agent itself and one for its anonymous class. That is exactly what javac is expected to do. Just inspect the output directory, and you will see it. Simply add the missing class file to the JAR and be happy:

    runCommand("jar -cfm agent.jar manifest.mf Agent.class Agent$1.class Transformer.class");
    

    Now the agent with the anonymous transformer class also works. I tested it.