ByteBuddy 附加到本地 运行 进程

ByteBuddy attach to a local running process

我正在尝试使用 ByteBuddy 附加到我计算机上的 运行 进程 运行。我希望在附加到 运行 程序时,我的代理会导致重新加载已加载的 类 并显示我的 Transformer 的打印语句。

相反,当我停止我所附加的 运行 进程时,我看到来自我的 Transformer 的一些打印语句,对于某些 JDK 类。

代码如下:

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");
    }
}

仅添加变压器不会导致重新加载已加载的 classes。默认情况下,你的转换器只会看到新加载的 classes,所以你在退出时看到一些 classes 的原因是这些 classes 以前没有使用过,而是专门为关机程序。

要重新转换 classes,您首先必须使用 addTransformer(yourTransformer, true) for registration, then invoke retransformClasses with the classes you want to transform. Mind the existence of getAllLoadedClasses and getInitiatedClasses(ClassLoader)

作为附加说明,我强烈反对采用将 Java 代理作为源代码字符串嵌入的方法,需要将它们写入临时文件、调用编译器并最终创建 jar 文件。您可以轻松地将 Agent classes 集成到您的普通源代码中。然后,要生成仅包含 Agent classes 的 jar 文件,您只需将应用程序代码库中已有的 .class 文件复制到 Agent jar。对于简单的情况,您可以同时使您的应用程序 jar 文件成为一个有效的代理 jar 文件,并且无需任何额外的复制步骤就可以使用它。

此外,请记住,对于所有 classes,ClassFileTransformer 应该始终 return null 它不会改变。返回原始 class 文件字节在语义上是相同的,但调用方需要额外的努力才能发现您没有更改它。对于将为每个加载的 classes 调用的代码,但通常只对少数几个感兴趣(或者只想打印信息而不更改任何内容),这样的性能问题很重要。