You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用自定义类加载器时ByteBuddy Agent向ClassReader传入错误数据

问题描述

我正在用基于ByteBuddy的Java Agent对类文件插桩,目标应用用自定义类加载器解密类文件。ByteBuddy转换类时触发了ASM的IllegalArgumentException,调试发现传入ClassReader的是加密的类文件缓冲区。请问如何让ByteBuddy使用类加载器解密后的缓冲区?

堆栈跟踪:

java.lang.IllegalArgumentException
  at net.bytebuddy.jar.asm.ClassReader.<init>(ClassReader.java:263)
  at net.bytebuddy.jar.asm.ClassReader.<init>(ClassReader.java:180)
  at net.bytebuddy.jar.asm.ClassReader.<init>(ClassReader.java:166)
  at net.bytebuddy.utility.OpenedClassReader.of(OpenedClassReader.java:130)
  at net.bytebuddy.utility.AsmClassReader$Factory$Default$4.make(AsmClassReader.java:134)
  at net.bytebuddy.utility.AsmClassReader$Factory$Default$2.make(AsmClassReader.java:108)
  at net.bytebuddy.utility.AsmClassReader$Factory$Default$1.make(AsmClassReader.java:94)
  at net.bytebuddy.utility.AsmClassReader$Factory$Default.make(AsmClassReader.java:187)
  at net.bytebuddy.pool.TypePool$Default.parse(TypePool.java:911)
  at net.bytebuddy.pool.TypePool$Default.doDescribe(TypePool.java:897)
  at net.bytebuddy.pool.TypePool$Default$WithLazyResolution.access$001(TypePool.java:977)
  at net.bytebuddy.pool.TypePool$Default$WithLazyResolution.doResolve(TypePool.java:1100)
  at net.bytebuddy.pool.TypePool$Default$WithLazyResolution$LazyTypeDescription.delegate(TypePool.java:1169)
  at net.bytebuddy.description.type.TypeDescription$AbstractBase$OfSimpleType$WithDelegation.getTypeVariables(TypeDescription.java:8594)
  at net.bytebuddy.description.type.TypeDescription$AbstractBase.isGenerified(TypeDescription.java:8200)
  at net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Reifying.onNonGenericType(TypeDescription.java:1796)
  at net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Reifying$1.onNonGenericType(TypeDescription.java:1752)
  at net.bytebuddy.description.type.TypeDescription$Generic$OfNonGenericType.accept(TypeDescription.java:3811)
  at net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection.accept(TypeDescription.java:6363)
  at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.analyzeNullable(MethodGraph.java:729)
  at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.doAnalyze(MethodGraph.java:743)
  at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.compile(MethodGraph.java:668)
  at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$AbstractBase.compile(MethodGraph.java:519)
  at net.bytebuddy.dynamic.scaffold.MethodRegistry$Default.prepare(MethodRegistry.java:472)
  at net.bytebuddy.dynamic.scaffold.inline.RebaseDynamicTypeBuilder.toTypeWriter(RebaseDynamicTypeBuilder.java:230)
  at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$UsingTypeWriter.make(DynamicType.java:4092)
  at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doTransform(AgentBuilder.java:12725)
  at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:12660)
  at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.access$1800(AgentBuilder.java:12369)
  at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$Java9CapableVmDispatcher.run(AgentBuilder.java:13151)
  at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$Java9CapableVmDispatcher.run(AgentBuilder.java:13081)
  at java.base/java.security.AccessController.doPrivileged(Native Method)
  at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doPrivileged(AgentBuilder.java)
  at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:12603)
  at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport.transform(Unknown Source)
  at java.instrument/sun.instrument.TransformerManager.transform(Unknown Source)
  at java.instrument/sun.instrument.InstrumentationImpl.transform(Unknown Source)
  at java.base/java.lang.ClassLoader.defineClass1(Native Method)
  at java.base/java.lang.ClassLoader.defineClass(Unknown Source)
  at java.base/java.lang.ClassLoader.defineClass(Unknown Source)
  at net.zk.CustomClassLoader.findClass(CustomClassLoader.java:19)
  at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
  at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
  at net.zk.Loader.main(Loader.java:9)
  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
  at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
  at java.base/java.lang.reflect.Method.invoke(Unknown Source)
  at net.zk.Injector.inject(Injector.java:16)
  at java.base/java.lang.Thread.run(Unknown Source)
解决方案

核心原因

Java Agent的默认转换时机是在类加载器调用defineClass之前,此时自定义类加载器还没完成解密,ByteBuddy拿到的是原始加密字节码,ASM无法解析。要解决这个问题,必须让ByteBuddy在解密之后再处理字节码。

方案1:修改自定义类加载器,调整转换顺序

在自定义类加载器的findClass方法中,先完成解密,再手动触发ByteBuddy的转换,最后调用defineClass。

示例代码:

public class CustomClassLoader extends ClassLoader {
    private final ClassFileTransformer byteBuddyTransformer;

    // 构造时传入ByteBuddy的转换器实例
    public CustomClassLoader(ClassLoader parent, ClassFileTransformer transformer) {
        super(parent);
        this.byteBuddyTransformer = transformer;
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        // 1. 读取加密的类文件
        byte[] encryptedBytes = loadEncryptedClassBytes(className);
        // 2. 解密字节码
        byte[] decryptedBytes = decryptClassBytes(encryptedBytes);
        
        // 3. 调用ByteBuddy转换解密后的字节码
        try {
            decryptedBytes = byteBuddyTransformer.transform(
                    this,
                    className,
                    null,
                    getProtectionDomain(),
                    decryptedBytes
            );
        } catch (IllegalClassFormatException e) {
            throw new RuntimeException("插桩失败: " + className, e);
        }
        
        // 4. 定义类
        return defineClass(className, decryptedBytes, 0, decryptedBytes.length);
    }

    // 省略加密类文件读取和解密的实现
    private byte[] loadEncryptedClassBytes(String className) { /* ... */ }
    private byte[] decryptClassBytes(byte[] encrypted) { /* ... */ }
}

方案2:自定义ByteBuddy的ClassFileLocator

通过自定义ClassFileLocator,让ByteBuddy在解析类元数据时,直接从自定义类加载器获取解密后的字节码,而非读取原始加密文件。

示例代码:

// 自定义ClassFileLocator,从自定义类加载器获取解密后的字节码
ClassFileLocator decryptedLocator = new ClassFileLocator() {
    @Override
    public Resolution locate(String typeName) {
        try {
            // 获取自定义类加载器实例
            CustomClassLoader customLoader = getYourCustomClassLoader();
            // 转换为资源路径
            String resourcePath = typeName.replace('.', '/') + ".class";
            
            try (InputStream inputStream = customLoader.getResourceAsStream(resourcePath)) {
                if (inputStream == null) {
                    return Resolution.ABSENT;
                }
                // 读取并解密字节码
                byte[] encryptedBytes = IOUtils.toByteArray(inputStream);
                byte[] decryptedBytes = customLoader.decryptClassBytes(encryptedBytes);
                return new Resolution.Explicit(decryptedBytes);
            }
        } catch (IOException e) {
            return Resolution.ABSENT;
        }
    }
};

// 配置ByteBuddy Agent使用自定义Locator
AgentBuilder agentBuilder = new AgentBuilder.Default()
        .with(AgentBuilder.LocationStrategy.Simple.of(decryptedLocator))
        .type(ElementMatchers.named("your.target.package.**"))
        .transform((builder, typeDesc, classLoader, module) -> {
            // 你的插桩逻辑,比如拦截方法
            return builder.method(ElementMatchers.any())
                    .intercept(MethodDelegation.to(YourInterceptor.class));
        });

方案3:使用ByteBuddy Advice机制(方法级插桩场景)

如果只需要对方法进行插桩,使用ByteBuddy的Advice可以避免提前解析整个类的字节码,减少对原始类文件的依赖。

示例代码:

AgentBuilder agentBuilder = new AgentBuilder.Default()
        .type(ElementMatchers.named("your.target.Class"))
        .transform((builder, typeDesc, classLoader, module) -> {
            return builder.visit(Advice.to(MethodTraceAdvice.class)
                    .on(ElementMatchers.anyMethod()));
        });

// 定义Advice类
public class MethodTraceAdvice {
    @Advice.OnMethodEnter
    public static void enter(@Advice.Origin Method method) {
        System.out.println("方法进入: " + method.getName());
    }
}

内容的提问来源于stack exchange,提问作者user30254631

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 10:48:10