freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

【安全研究】JavaAgent技术在内存马中的应用
2022-03-03 17:10:06
所属地 福建省
JavaAgent技术简介

JDK1.5开始引入了Agent机制(即启动java程序时添加“-javaagent”参数,Java Agent机制允许用户在JVM加载class文件的时候先加载自己编写的Agent文件,通过修改JVM传入的字节码来实现注入自定义的代码。采用这种方式时,必须在容器启动时添加jvm参数,所以需要重启Web容器。

JDK1.6新增了attach方式,可以对运行中的java进程附加agent,提供了动态修改运行中已经被加载的类的途径。一般通过VirtualMachine的attach(pid)方法获得VirtualMachine实例,随后可调用loadagent方法将JavaAgent的jar包加载到目标JVM中。

下面一个章节笔者将通过两个demo案例说明JavaAgent技术的两种方式,让读者明白premain和agentmain的具体原理。

JavaAgent两种方式

1、Premain

创建一个sayHello类,写一个say()方法。

public class sayHello {

public String say() {

return "hello,world!";

}

}

创建一个People类,运行say()方法,输出结果为:hello,world!

public class People {

public static void main(String[] args) {

System.out.println(new sayHello().say());

}

}

创建Transformer重写transformer方法,实现修改传入JVM的字节码。笔者这里通过javassist对类字节码进行处理。

package org.example;

import javassist.*;

import java.io.IOException;

import java.lang.instrument.ClassFileTransformer;

import java.lang.instrument.IllegalClassFormatException;

import java.security.ProtectionDomain;

public class Transformer implements ClassFileTransformer {

@Override

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

System.out.println(className);

if (className.endsWith("sayHello")){

try {

final ClassPool classPool = ClassPool.getDefault(); // 创建ClassPool对象

final CtClass ctClass = classPool.get("org.example.sayHello");

CtMethod ctMethod = ctClass.getDeclaredMethod("say"); // 获取成员方法

String methodBody = "return \"hello premain\";";

ctMethod.setBody(methodBody); //替换方法体中所有内容

byte[] bytes = ctClass.toBytecode(); //使用类CtClass,生成类二进制

//调用CtClass对象的detach()方法 CtClass对象从ClassPool移除掉减少内存消耗

ctClass.detach();

return bytes;

} catch (NotFoundException e) {

e.printStackTrace();

} catch (CannotCompileException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

return null;

}

}

定义Premain类的premain方法

package org.example;

import java.lang.instrument.Instrumentation;

public class Premain {

public static void premain(String agentArgs, Instrumentation inst){

System.out.println("premain agent run!");

inst.addTransformer(new Transformer());

}

}

使用Maven打包成TestPremain-1.0-SNAPSHOT.jar文件,需要如下修改pom.xml文件。把<Premain-class>设置为premain方法所在类。

<plugin>

<artifactId>maven-jar-plugin</artifactId>

<version>3.0.2</version>

<configuration>

<archive>

<manifestEntries>

<Premain-class>org.example.Premain</Premain-class>

<Can-Redefine-Classes>true</Can-Redefine-Classes>

<Can-Retransform-Classes>true</Can-Retransform-Classes>

</manifestEntries>

</archive>

</configuration>

</plugin>

在运行配置中添加vm选项

G87FV6fW_4vqp.png

图1

运行结果如图2所示,修改了say方法。

fqFzKDWH_X8zk.png

图2

2、Agentmain

同premain也创建一个People类循环打印字符串,代码如下所示。

package org.example;

public class People {

public void sayHello(String name) {

System.out.println(String.format("%s say hello!", name));

}

public static void main(String[] args) throws InterruptedException {

People p = new People();

for (;;){

Thread.sleep(1000);

p.sayHello(Thread.currentThread().getName());

}

}

}

重写transform方法,注入进程后打印输出代理的类,代码如下所示。

package org.example;

import java.lang.instrument.ClassFileTransformer;

import java.lang.instrument.IllegalClassFormatException;

import java.security.ProtectionDomain;

public class Transform implements ClassFileTransformer {

@Override

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

System.out.println(String.format("agent run target class= %s", className));

return classfileBuffer;

}

}

新建Agent类实现agentmain方法,代码如下所示

public class Agent {

public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {

inst.addTransformer(new Transform(),true);

inst.retransformClasses(Class.forName("org.example.People"));

}

}

将Agent设置为<Agent-Class>并打包成为jar文件。Pom.xml文件如下所示,值得注意的是如果需要修改已经被JVM加载过的类的字节码,那么还需要在MANIFEST.MF中添加Can-Retransform-Classes:true或Can-Redefine-Classes:true。

<Agent-Class>org.example.Agent</Agent-Class>

<Can-Retransform-Classes>true</Can-Retransform-Classes>

创建Attach类注入目标类的进程,代码如下所示。

public class Attach {

public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {

String agentPath = "F:\\IdeaProjects\\TestAgent\\target\\TestAgent-1.0-SNAPSHOT.jar";

List<VirtualMachineDescriptor> list = VirtualMachine.list(); //获取本机所有运行的Java进程

for (VirtualMachineDescriptor desc :list){

if (desc.displayName().endsWith("People")){

VirtualMachine vm = VirtualMachine.attach(desc.id());

vm.loadAgent(agentPath);

vm.detach();

}

}

}

}

Attach捕获到类进程号如图3所示。

EZ94SKDZ_OnME.png图3

先运行people类在运行attach,运行结果如图4所示。

Zaw52RET_Z32r.png

图4

分析JavaAgent型内存马

由上文可知Agentmain可实现最重要的三个类Agent Attach Transform,来分析冰蝎作者之前写的memshell实现原理,项目地址:https://github.com/rebeyond/memShell.gitMemshell中Transform类代码如图5所示。

COh3Lh2k_mEqe.png图5

不同于前面章节的demo,这里除了使用ClassPool.getDefault()还使用ClassClassPath搜索class路径其原理是:ClassPool.getDefault()获取的ClassPool使用JVM的classpath。在Tomcat等Web服务器运行时,服务器会使用多个类加载器作为系统类加载器,这可能导致ClassPool可能无法找到用户的类。这时,ClassPool须添加额外的classpath才能搜索到用户的类。

CtClass cc = cp.get("org.apache.catalina.core.ApplicationFilterChain");

CtMethod m = cc.getDeclaredMethod("internalDoFilter");

m.addLocalVariable("elapsedTime", CtClass.longType);

m.insertBefore(readSource());

如上代码:作者Hook了ApplicationFilterChain中的internalDoFilter方法,然后定义一个long类型的属性,elapsedTime,并通过insertBefore方法将source.txt中内容插入到方法内容的开始处。source.txt是url参数和agent交互的逻辑,如图6所示。

vEkzd47B_udch.png图6

笔者之前用此内存马时发现两个特点:第一是该内存马会自己删除jar包,实现代码如下。

V2I8gRb9_wCv2.png图7

第二点是重启tomcat服务之后内存马还是存在,只有通过jps-l kill掉进程后启动服务才能删除内存马,其原理是使用了ShutdownHook机制。

deW8QeSA_qfKF.png图8

通过使用Runtime.addShutdownHook(Thread hook)方法注册JVM关闭的勾子,调用writeFiles方法把jar包落地磁盘,再通过Runtime.exec启动java-jar inject.jar。

由于Hook的关键函数ApplicationFilterChain.internalDoFilter是tomcat的方法,导致其他中间件不适用,在冰蝎3.0中的内存马作者更改了Hook点。(源码版本为V3.0 Beta11_t00ls)在agentmain中做了一个判断,如果是Tomcat选择hook javax.servlet.http.HttpServlet中的service方法,如果是weblogic选择hookweblogic.servlet.internal.ServletStubImpl中的execute方法。

代码如图9所示。

gLeWaLaH_tePs.png图9

在jdk9及以后的版本不允许SelfAttach(即无法attach自身的进程)。修改前面章节Attach demo,将jdk换成9之后的,attach自身的PID会报错提示Can not attach to current VM。代码如下,报错截图如图10所示。

public class Attach {

public static void main(String[] args) throws Exception {

List<VirtualMachineDescriptor> list = VirtualMachine.list();

for(VirtualMachineDescriptor desc : list){

System.out.println("进程ID:" + desc.id() + ",进程名称:" + desc.displayName());

}

Scanner myObj = new Scanner(System.in);

System.out.println("输入要注入的进程:");

String pid = myObj.nextLine();

String agentPath = "F:\\IdeaProjects\\TestAgent\\target\\TestAgent-1.0-SNAPSHOT.jar";

VirtualMachine vm = VirtualMachine.attach(pid);

vm.loadAgent(agentPath);

vm.detach();

}

}

4WhQHz9R_w3ab.png

图10

看到Rebeyond师傅在《Java内存攻击技术漫谈》中提出一种方法,绕过allowAttachSelf。首先Debug attch执行流程,如图11所示。可以发现attach的时候会创建一个HotSpotVirtualMachine的父类对象,取键值对jdk.attach.allowAttachSelf的值计算后保存到ALLOW_ATTACH_SELF中,可通过反射修改该属性值。

McSzCgTd_QGCZ.png

图11

ALLOW_ATTACH_SELF字段有final修饰符,需要设置setAccessible(true);具体代码如下所示。

Class cls=Class.forName("sun.tools.attach.HotSpotVirtualMachine");

Field field=cls.getDeclaredField("ALLOW_ATTACH_SELF");

field.setAccessible(true);

Field modifiersField=Field.class.getDeclaredField("modifiers");

modifiersField.setAccessible(true);

modifiersField.setInt(field,field.getModifiers()&~Modifier.FINAL);

field.setBoolean(null,true);

修改后会弹出警告信息如图12所示,成功注入结果如图13所示。

KXe99kOy_EXGt.png图12

JaCnSTrd_MXWB.png图13

回到冰蝎3.0源码中,通过setProperty将jdk.attach.allowAttachSelf设置为true,实现绕过SelfAttach。

System.setProperty("jdk.attach.allowAttachSelf", "true");

总结

本文从permain和agentmain两种实现JavaAgent的原理方法引入到java agent在内存马中的应用,通过分析memshell到冰蝎3.0内存马源码,加深了对agent型内存马Hook的关键函数、持久化方法以及绕过SelfAttach方法等内存马技术点的理解与学习,希望对读者有帮助。

# 冰蝎 # 内存马
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者