Java 内存马第四篇 - Agent 内存马

四、 Java Agent 内存马

4.1 Java Agent示例

对于Agent(代理)来讲,其大致可以分为两种,一种是在JVM启动前加载的**premain-Agent**,另一种是JVM启动之后加载的**agentmain-Agent**。这里我们可以将其理解成一种特殊的Interceptor(拦截器),如下图

premain-Agent

agentmain-Agent

4.1.1 premain-Agent

使用外部 MANIFEST.MF 文件 创建

创建Java Agent类

此处不写包名,避免后续的编译的 jar 包找不到类

创建MANIFEST.MF文件

最后必须有一个空行!

​

创建测试主程序
目录结构:

img

编译

创建俩个文件夹,

img

img

创建Agent JAR文件

img

测试运行

img

4.1.2 agentmain-Agent

agentmain-Agent能够在JVM启动之后加载并实现相应的修改字节码功能。

premain方法在JDK1.5中提供,在JDK版本为1.5时,开发者只能在main加载之前添加手脚,但是对于大部分内存马注入时,都是JVM已经运行的情况下。在JDK1.6中实现了attach-on-demand,我们可以使用AttachAPI动态的加载Agent,agentmain能够在JVM启动后加载并实现相应的修改字节码的功能。

AttachAPI在tool.jar中,而JVM启动时默认不加载该依赖,需要我们在classpath中额外进行指定

VirtualMachine

VirtualMachine 可以实现获取 JVM 信息、内存dump、现成dump、类信息统计(例如JVM加载的类)等功能。

该类允许我们通过给attach方法传入一个JVM的PID,来远程连接到该JVM上 ,之后我们就可以对连接的JVM进行各种操作,如注入Agent。下面是该类的主要方法

VirtualMachineDescriptor 类

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

示例:

pom.xml:

Missing artifact com.sun:tools:jar:1.8.0有效解决办法(亲测)-CSDN博客

img

实现 agentmain-Agent

编写一个 Sleep_Hello类,模拟正在运行的 JVM

编写JavaAgentAgentmain

配置 MANIFEST.MF 文件

之后编译得到 Agentmain.jar

img

编写测试类Inject_Agent,可以将agent注入到正在运行的JVM中

先运行 SleepHello.java 再运行 InjectAgent.java

img

4.2 动态修改字节码

4.2.1 Javassist

用来处理Java字节码的类库,允许在已经编译好的类中添加新的方法,或者修改已有的方法,同时也可以通过手动的方式去生成一个新的类对象

简介

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

类池 ClassPool

ClassPool是 CtClass 对象的容器。它按需读取类文件来构造 CtClass 对象,并且保存 CtClass 对象以便以后使用。需要注意的是 ClassPool 会在内存中维护所有被它创建过的 CtClass,当 CtClass 数量过多时,会占用大量的内存,API 中给出的解决方案是有意识的调用 CtClass 的 detach() 方法以释放内存。

主要方法有以下几个:

  • getDefault:获取默认的 ClassPool 对象。
  • get、getCtClass:根据类名获取 CtClass 对象,用于操作类的字节码。
  • makeClass:创建一个新的 CtClass 对象,用于新增类。
  • insertClassPath、appendClassPath:插入类搜索路径,提供给类加载器用于加载类。
  • toClass:将修改后的 CtClass 加载至当前线程的上下文类加载器中。通过调用 CtClass 的 toClass() 方法实现了将 CtClass 转换为 Class 对象,这样就可以在运行时使用这个类。需要注意的是一旦调用该方法,则无法继续修改已经被加载的 Class 对象。

获得方法如下:

通过ClassPool.getDefault()使用JVM的类搜索路径。如果程序运行在JBoss或者Tomcat的Web服务器上,则ClassPool可能无法找到用户的类,因为Web服务器会使用多个类加载器作为系统类加载器。在这种情况下,ClassPool必须添加额外的搜索路径

CtClass

CtClass 是 Javassist 中的一个抽象类,用于表示一个类文件。

CtClass 需要关注的方法:

  • freeze:冻结一个类,使其不可修改。
  • isFrozen:判断一个类是否已被冻结。
  • prune:删除类不必要的属性,以减少内存占用。调用该方法后,许多方法无法将无法正常使用,慎用。
  • defrost:解冻一个类,使其可以被修改。如果事先知道一个类会被 defrost , 则禁止调用 prune 方法。
  • detach:将该 class 从 ClassPool 中删除。
  • setSuperclass:设置当前类的父类。
  • writeFile:将 CtClass 对象转换为类文件并将其写入本地磁盘。
  • toClass:通过类加载器加载该 CtClass ,示例:Class clazz = cc.toClass(); 。
  • toBytecode:获取 CtClass 的字节码,示例:byte[] b = cc.toBytecode(); 。

可以通过以下代码获取

CtMethod 和 CtField

CtMethod 和 CtField 分别代表 Java 类中的方法和字段。通过 CtClass 对象,可以获取、添加、删除或修改类中的方法和字段。这些对象提供了丰富的 API ,用于操作方法和字段的各种属性,如访问修饰符、名称、返回类型等。

CtMethod 中的一些重要方法:

  1. insertBefore:在方法的起始位置插入代码。
  2. insterAfter:在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到 exception 。
  3. insertAt:在指定的位置插入代码。
  4. setBody:将方法的内容设置为要写入的代码,当方法被 abstract 修饰时,该修饰符被移除。
  5. make:创建一个新的方法。

利用 CtMethod 中的 insertBefore,insterAfter,insertAt 等方法可以实现 AOP 增强功能。

通过CtClass.getDeclaredMethod(MethodName)获取,其中该类提供了一些方法可以让我们直接修改方法体,方法如下。

其中传递给insertBefore、insertAfter和insertAt的String对象是由Javassist的编译器进行编译的。该编译器支持语言的扩展,以下以$符号开头的几个标识符具有特殊的含义:

img

Javassist 使用

依赖:

使用 javassist 创建一个 Person 类

img

创建好的 Person.class

使用Javassist生成恶意class

从字节码层面生成恶意 class ,跳过恶意类的编译过程

img

4.2.2 Instrumentation 动态修改字节码

Instrumentation是 JVMTIAgent(JVM Tool Interface Agent)的一部分,Java agent通过这个类和目标 JVM 进行交互,从而达到修改数据的效果。

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

获取目标JVM已加载类

创建 jar

img

使用 InjectAgent.java 测试

img

能够获取目标JVM已加载类

img

transform - 转换器

在Instrumentation接口中,我们可以通过**addTransformer()**来添加一个transformer(转换器),关键属性就是ClassFileTransformer类。

ClassFileTransformer接口中只有一个**transform()**方法,返回值为字节数组,作为转换后的字节码注入到目标JVM中。

img

在通过 addTransformer 注册一个transformer后,每次定义或者重定义新类都会调用transformer。所谓定义,即是通过ClassLoader.defineClass加载进来的类。而重定义是通过Instrumentation.redefineClasses方法重定义的类。

当存在多个转换器时,转换将由 transform 调用链组成。 也就是说,一个 transform 调用返回的 byte 数组将成为下一个调用的输入(通过 classfileBuffer 参数)。

转换将按以下顺序应用:

至于transformer中对字节码的具体操作,则需要使用到Javassisit类。

Instrumentation的局限性

大多数情况下,我们使用Instrumentation都是使用其字节码插桩的功能,简单来说就是类重定义功能(Class Redefine),但是有以下局限性:

premain和agentmain两种方式修改字节码的时机都是类文件加载之后,也就是说必须要带有Class类型的参数,不能通过字节码文件和自定义的类名重新定义一个本来不存在的类。

类的字节码修改称为类转换(Class Transform),类转换其实最终都回归到类重定义Instrumentation#redefineClasses方法,此方法有以下限制:

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

4.3 Agent 内存马注入

看了许多师傅关于Agent内存马注入的文章和博客,各有千秋,利用方式也各不相同。现在,我根据自己的理解来编写一个小demo。

4.3.1 方式一(后续可能会复现其他师傅的利用方式)

思路:

  1. 编译并打包 AgentMemShell 为 agentmemshell.jar。
  2. 编译 Injector(需要 tools.jar,通常在 JAVA_HOME/lib/ 下)。
  3. 运行 Injector,指定目标 Tomcat 的 PID。
  4. 访问任何 Servlet 路径,带上 ?cmd=whoami 参数,即可执行命令。

可用 Demo:

目录结构:

img

pom.xml
AgentMemShell.java
ServletTransformer.java
Injector.java
MANIFEST.MF

这是 一个小脚本:

build.bat

(部分内容根据实际情况自行修改)

执行 build.bat

img

开始测试

启动一个 Tomcat

img

img

Tomcat 进程

img

找到进程:40268 org.apache.catalina.startup.Bootstrap

测试注入

使用管理员身份运行!!

(为了防止一些莫名其妙的错误,使用了绝对路径)

img

注入成功

img

Tomcat 日志:

可以执行命令:

(:但是当前的内存马只是部分生效,/hello-servlet 路径可以执行,其他不可以

img

img

4.3.2 方式二

环境

SpringBoot 2.6.13

CommonCollection 3.2.1

搭建一个反序列化环境:

img

寻找关键类

在Filter中存在doFilter方法,除了会对我们的请求进行过滤,会依次调用FilterChains中的Filter,同时在 ApplicationFilterChain#doFilter 中还封装了我们用户请求的 request 和 response ,那么如果我们能够注入该方法,那么我们不就可以直接获取用户的请求,将执行结果写在 response 中进行返回。

img

反序列化注入

注入流程如下:

  1. 编写 agent.jar 从而实现 org.apache.catalina.core.ApplicationFilterChain#doFilter 进行字节码修改
  2. 利用 CC依赖 的反序列化漏洞将我们的加载代码打进去,然后使其执行来加载我们的 agent.jar

agent.jar

pom.xml

AgentMain.java

DefineTransformer.java

maven 打包后 得到的 target/Demo02-1.0-SNAPSHOT.jar 就是我们需要的 agent.jar

img

编写java代码来将其加载进JVM中

其中大致思路为获取到JVM的PID,调用 loadAgent 方法将 agent.jar 注入

InjectMain.java

img

CC 反序列化注入

通过CC反序列化进行注入Agent内存马, 编译生成 Test.class

用 java-chains , 把 Test.class 注入到 TemplatesImpl 的 _bytecodes,生成了 JavaNativePayload_…txt 文件。

img

img

img

img

  1. 内存马注入情况

从日志来看:

Running JVM list …

id »> 40008

Find the Inject Class: org.apache.catalina.core.ApplicationFilterChain

Agent.jar Inject Success !!

使用的 Demo02-1.0-SNAPSHOT.jar 成功 attach 到了目标 JVM(PID 40008)。

org.apache.catalina.core.ApplicationFilterChain 作为 hook 入口已经被找到。

也就是说,内存马的 Agent 部分已经成功加载到 JVM。

所以注入是成功的,这部分没有问题

curl 请求触发了 POST /cc,但是返回:

分析原因:

你上传的 payload 是基于 CommonsCollections K3 + TemplatesImpl 的序列化。

错误发生在 TemplatesImpl 初始化阶段的 postInitialization 方法:

NullPointerException 一般是因为 TemplatesImpl 中 _bytecodes 或 _name 等字段为 null。

也就是说 test.ser 文件中,TemplatesImpl 对象没有正确填充字节码….

为什么生成的时候没有写进去呢?

参考:

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

Java安全学习——内存马 - 枫のBlog

Spring内存马 | CurlySean’s Blog

Java Agent实现反序列化注入内存shell

JavaWeb 内存马一周目通关攻略 | 素十八

JavaWeb 内存马二周目通关攻略 | 素十八

Shell中的幽灵王者—JAVAWEB 内存马 【认知篇】 - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

Java内存马——Tomcat Valve型的三种注入 - FreeBuf网络安全行业门户

Java 内存马(四):Spring Boot Controller 内存马 | 渐怀的博客

Spring型内存马分析

Servlet 简介 | 菜鸟教程

Spring内存马学习

Spring内存马——Controller/Interceptor构造

[基础篇 - Javassist 使用指南](https://changeyourway.github.io/2024/06/07/Java 安全/基础篇-javassist用法指南/)

Java安全学习——ROME反序列化 - 枫のBlog (使用Javassist缩短恶意class)

Java Agent实现反序列化注入内存shell

Agent内存马 | CurlySean’s Blog