JAVA Agent实现RASP技术(一)---premain

RASP技术全称(Runtime Application Self Protect)运行态应用自防护技术,是Gartner提出未来应用安全领域的重要技术,由应用自身做好危险函数执行的防护和阻断。RASP技术的实现基于Hook技术,通过反射、代理等技术,监控应用运行的细节内容(精确到类的加载,方法的执行),并阻断危险动作的运行。基于RASP技术,可以使应用在非侵入的可情况下,实现漏洞的整改与防护。

基于JAVA的RASP防护,目前普遍从java.lang.instrument入手,其自JDK1.5后引入,主要目的是为开发人员提供监控和记录整个JVM运行的方法。

JAVA Instrumentation Agent

JAVA中基于Instrumentation类约定俗成了两个类方法名,premain和agentmain。官方文档见https://docs.oracle.com/javase/9/docs/api/java/lang/instrument/package-summary.html

  1. premain方法
  • 定义方式:public static void premain(String agentArgs, Instrumentation inst)
  • 使用方法:将agent.jar在应用运行前通过java -javaagent:<jar 路径> -jar 应用程序.jar,绑定JVM,实现class文件的修改。
  1. agentmain方法
  • 定义方式:public static void agentmain(String agentArgs, Instrumentation inst)
  • 使用方法:希望修改的类正在运行状态,我们将agent.jar文件主动运行,agent程序会获取目前所有运行的JVM,通过过滤条件,找到希望修改的jvm-id,然后通过VirtualMachine.attach()方法,将agent注入,实现class文件的修改。

premain工作示意图

类都是classloader载入的,使用agent后,修改动作是在如下场景时生效:

  1. addTransformer,当class被装载时(loadclasss),与ClassFileTransformer(我们需要实现其transform方法)结合,转换单个class文件(可以根据拿到的classname进行匹配)

  2. redefineClasses,支持多个class文件的转换(自己指定好要转换哪些类,比如类aaa,即aaa.class)

接下来,介绍一下premain的实现示例。

premain-agent

测试目标程序

  • Main.java
package org.gongz;

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world!");
        People p = new People();
        p.setAge(18);
        p.setName("杨超越");
        System.out.println(p.toString());
    }
}
  • People.java
package org.gongz;

public class People {
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "People{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

通过maven将项目打成一个jar包,并在pom.xml中指明主类,防止运行报错。可参考https://blog.csdn.net/londa/article/details/115098901学习maven打包相关知识。

  • 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.gongz</groupId>
    <artifactId>helloWorld</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                        <!--自动添加META-INF/MANIFEST.MF -->
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Main-Class>org.gongz.Main</Main-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

打包成helloWorld.jar,测试目标程序修改前运行效果如下:
修改前运行效果

premain agent程序

  • PreAgent.java
package org.gongz;

import java.lang.instrument.Instrumentation;

/*
Instrumentation类可以与JVMI通讯,实现类加载时获取类信息,当然也可以改变类信息。
改变类信息就涉及到修改加载到内存中的java的字节码(class文件)
修改字节码工具有两个,一个是javassist,较为人性化,效率稍低。
另一个是asm,偏底层,复杂但效率高。

修改class字节码是通过实现ClassFileTransformer接口,并将实现类注册到Instrumentation中。
这样一旦有class的加载或者重载,这个修改就会生效。
*/
public class PreAgent {
    // premain方法
    public static void premain(String args, Instrumentation inst) throws Exception {
        inst.addTransformer(new ClassHacker(), true);
    }
}
  • ClassHacker.java
package org.gongz;

import javassist.*;

import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class ClassHacker implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        if(className.equals("org/gongz/People")){
            System.out.println("loader:"+loader);
            System.out.println("className:"+className);
            String loadName = className.replaceAll("/", ".");
            try {
                // 使用类加载器和原始class文件二进制码创建pool、获取类
                ClassPool classPool = new ClassPool();
                classPool.appendClassPath(new LoaderClassPath(loader));
                CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
                // 网上找到其他教程大多使用下面这种方法获取class文件,我试了好像也可以
                // stackoverflow中我找到类上面的获取方式,我觉得也挺合理的,试了也可以,就是用来上面的代码
                // ClassPool classPool = ClassPool.getDefault();
                // CtClass ctClass = classPool.get(loadName);
                // 获取类中的方法
                CtMethod ctMethod = ctClass.getDeclaredMethod("setName");
                ctMethod.insertAfter("this.name=\"鞠婧祎\";");
                byte[] newClassBytes = ctClass.toBytecode();
                ctClass.detach();
                return newClassBytes;
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
        return classfileBuffer;
    }
}

注意,由于修改class文件这里使用了javassist包,所以我们要在maven的pom.xml中引入依赖,同时打包时要注意将依赖一起打到jar包中,否则agent程序运行会出现javassist找不到的情况。

这里还有一个坑点,我之前在网上看的资料引入javassist时,都会使用<groupId>javassist</groupId>这个groupId,而最新的Id应该是org.javassist。这导致maven中匹配的版本不对,找不到包,只有版本非常低的。而版本非常低的javassist无法修改较新版本的jdk的class文件。这里推荐大家使用新版本的javassist,避免遇到无法找到类,或者无法修改等各种各样奇怪的问题。

  • 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.gongz</groupId>
    <artifactId>premainDemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.29.2-GA</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <!--自动添加META-INF/MANIFEST.MF -->
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Premain-Class>org.gongz.PreAgent</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

通过maven打包,会生成两个agent.jar的包,一个带依赖、一个不带依赖,我们选择后缀带依赖的包。

运行测试

java -jar -javaagent:./premainDemo-1.0-SNAPSHOT-jar-with-dependencies.jar helloWorld-1.0-SNAPSHOT.jar进行agent运行和修改,得到以下结果,我们可以看到人名已经被修改成我们希望的值了。
agent修改后的运行

总结

基于上述的操作方法,我们可以获取到类的详细信息,甚至修改类的逻辑代码。有了这样的方式,我们就可以将其运用到java的rasp技术中去,在agent中定义需要观察的类和方法,对其进行日志记录和监控,发现威胁时直接修改类的代码组织其运行,具体我也在学习中,这里只做一个简单的实现。

通过这次实验,我也发现了java通过premain和agentmain给了开发者非常大的权利去控制其他jvm的行为,这种权限最先是希望开发者更好的监控程序的运行,反之也可以被运用于安全防护和破坏,还是非常具有危险性的一个功能。

posted @ 2023-05-12 16:26  宁宁鸡a  阅读(532)  评论(0编辑  收藏  举报