使用自定义类加载器时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
相关产品推荐
相关产品推荐

