当前位置:   article > 正文

【安全狗技术研究】JavaAgent技术在内存马中的应用_java agent 安全

java agent 安全

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方法所在类。

maven-jar-plugin

3.0.2

org.example.Premain

true

true

在运行配置中添加vm选项

图1

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

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

创建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 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所示。

图3

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

图4

分析JavaAgent型内存马

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

图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所示。

图6

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

图7

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

图8

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

由于Hook的关键函数,即,ApplicationFilterChain.internalDoFilter是tomcat的方法,导致其他中间件不适用,在冰蝎3.0中的内存马作者更改了Hook点(源码版本为V3.0Beta11_t00ls)。

在agentmain中做了一个判断,如果是Tomcat选hookjavax.servlet.http.HttpServlet中的service方法,如果是weblogic选择hookweblogic.servlet.internal.ServletStubImpl中的execute方法。

代码如图9所示。

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

}

}

图10

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

图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所示。

图12

图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方法等内存马技术点的理解与学习,希望对读者有帮助。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/450780
推荐阅读
相关标签
  

闽ICP备14008679号