简体   繁体   中英

ByteBuddy attach to a local running process

I'm attempting to use ByteBuddy to attach to a running process running on my computer. I expect that at the time I attach to the running program, my Agent will cause the loaded classes to be re-loaded and for my Transformer's print statements to show up.

Instead what happens is that when I stop the running process I am attaching to, I see some print statements from my Transformer, for some JDK classes.

Code posted below :

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.implementation.FixedValue;

import java.io.*;

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

public class Thief {

    public static void main(String[] args) throws Throwable {
        String pid = "86476"; // <-- modify this to attach to any java process running on your computer
        System.out.println(new Thief().guessSecurityCode(pid));
    }

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


    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 Throwable {
        writeFile("Agent.java", generateSimpleAgent());
        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");
    }
}

Just adding a transformer does not cause reloading of already loaded classes. Your transformer will by default only see newly loaded classes, so the reason you're seeing some classes on exit is that these classes were not used before but loaded specifically for the shutdown procedure.

To retransform classes you first have to use addTransformer(yourTransformer, true) for registration, then invoke retransformClasses with the classes you want to transform. Mind the existence of getAllLoadedClasses and getInitiatedClasses(ClassLoader)

As an additional note, I strongly discourage to pursue the approach of embedding the Java Agent as source code strings, needing to write them to temporary files, invoke the compiler and eventually creating the jar file. You can easily integrate the Agent classes into your ordinary source code. Then, to generate a jar file containing the Agent classes only, you just need to copy the already existing .class files from your application's code base to the Agent jar. For simple cases, you can make your application jar file a valid Agent jar file at the same time and just use that without any additional copying step.

Further, keep in mind that a ClassFileTransformer should always return null for all classes it doesn't change. Returning the original class file bytes would be semantically the same, but it requires additional effort on the caller's side to find out that you didn't change it. For code that will be invoked for every loaded classes, but is usually only interested in a few (or just wants to print information without changing anything), such performance issues matter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM