• 从零开始的Java RASP实现(一)


    0 从零开始的Java RASP实现(一)

    本科毕设做过Python的RASP之后,对这项技术很有兴趣,当时OpenRASP开始出现,并且Java的实现非常接近真正的运行时防御的概念。一直没有时间和足够的动力学习Java,最近一口气学了不少Java相关的东西,准备从反序列化和RASP两个方向继续深入学一下Java。这边笔记主要也是记录整个学习过程,目标是仿照OpenRASP的java实现,完成一个Java的RASP。

    1 javaagent

    1.1 Main方法启动前

    概念介绍:

    javaagent是java命令提供的一个参数,这个参数可以指定一个jar包,在真正的程序没有运行之前先运行指定的jar包。并且对jar包有两个要求:

    • jar包的MANIFEST.MF文件必须指定Premain-Class
    • Premain-Class指定的类必须实现premain()方法。

    这个premain方法会在java命令行指定的main函数之前运行。

    在java命令参数种,还可以看到其它参数,例如

    -agentlib:<libname>[=<选项>]
                      加载本机代理库 <libname>, 例如 -agentlib:hprof
                      另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help
    -agentpath:<pathname>[=<选项>]
                      按完整路径名加载本机代理库
    -javaagent:<jarpath>[=<选项>]
                      加载 Java 编程语言代理, 请参阅 java.lang.instrument
    

    javaagent可以指定很多个,jvm会依次执行不同的jar。前面提到的premain方法有两种定义方式

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

    根据sun.instrument.InstrumentationImpl 的源代码,可以知道,会优先调用第一种写法。这种方法可以在JDK1.5及之后的版本使用

    如何使用

    使用javaagent需要几个步骤:

    • 定义一个MANIFEST.MF文件,必须包含Premain-Class选项,也需要加入Can-Redefine-Classes和Can-Retransform-Classes选项,后面两个选项看名字就知道意思
    • 创建Premain-Class指定的类,类中包含premain方法,该方法可以进一步加载RASP实现原理
    • 将MANIFEST.MF和写好的各种类打包成jar
    • 启动java时,添加-javaagent:xx.jar,即可让java先自动执行写好的premain方法

    在premain方法执行时,获取的Instrumentation对象还会加载大部分类,包括后面main方法执行时需要加载的各种类,但是抓不到系统类。也就是说,在这个位置在main方法执行前,就可以拦截或者重写类,结合ASM、javassist、cglib方式就可以实现对类的改写或者插桩。

    创建agent

    现在来实现一下,目录结构如下

    -java-agent
    ----src
    ----|----main
    ----|----------java
    ----|--------------com
    ----|-------------------bitterz
    ----|----------------------PreMain
    ----|pom.xml
    

    pom.xml使用idea创建maven项目自带的pom.xml即可,然后加入如下配置

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <Premain-Class>com.bitterz.PreMain</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
    

    修改里面的premain即可,这样配置之后,直接使用idea的maven->package就可以打包成一个可以使用的agent.jar。

    com.bitterz.PreMain如下:

    package com.bitterz;
    
    import java.lang.instrument.ClassFileTransformer;
    import java.lang.instrument.IllegalClassFormatException;
    import java.lang.instrument.Instrumentation;
    import java.security.ProtectionDomain;
    
    public class PreMain {
            public static void premain(String agentArgs, Instrumentation inst){
                System.out.println("agentArgs:" + agentArgs);
                inst.addTransformer(new DefineTransformer(), true);
            }
    
            static class DefineTransformer implements ClassFileTransformer{
                @Override
                public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                    System.out.println("premain load Class: " + className);  // 注意这里的输出
                    return classfileBuffer;
                }
            }
    }
    

    创建main

    然后再创建一个要执行的main

    package com.bitterz;
    
    public class Main {
        public static void main(String[] args) {
            System.out.println("Main.main() in test project");
        }
    }
    

    同样修改pom.xml配置,只需要再<manifestEntries>标签下添加一个<Main-Class>即可用idea的maven打包成一个jar,并且指定了main类。

    两个项目用maven打包后,在命令行执行一下

    可以看到premain先输出了加载的各种类,包括com.bitterz.Main,然后才是真正的main执行,最后结束时,premain还加载了一些结束时需要的类。到此,在启动main方法前,实现对后续类的加载或进一步修改操作,已经有了雏形

    1.2 JVM启动后

    前面的方法需要在main函数启动前,执行agent,但有些时候,jvm已经启动了,而且服务不能轻易暂停,但这个时候还是想对jvm中的类做一些修改应该怎么办呢?

    attach机制

    这就要引入attch机制了,jdk1.6之后在Instrumentation中添加了一种agentmain的代理方法,可以在main函数执行之后再运行。和premain函数一样,开发者可以编写一个包含agentmain函数的Java类,它也有两种写法:

    public static void agentmain (String agentArgs, Instrumentation inst)
    
    public static void agentmain (String agentArgs)
    

    同样的,带有Instrumentation的方法会被优先调用,开发者必须再MANIFEST.MF文件中设置Agent-Class来指定包含agentmain函数的类。

    这种attach机制的具体实现在com.sun.tools.attach中,有如下两个类:

    • VirtualMachine 字面意义表示一个Java 虚拟机,也就是程序需要监控的目标虚拟机,提供了获取系统信息(比如获取内存dump、线程dump,类信息统计(比如已加载的类以及实例个数等), loadAgent,Attach 和 Detach (Attach 动作的相反行为,从 JVM 上面解除一个代理)等方法,可以实现的功能可以说非常之强大 。该类允许我们通过给attach方法传入一个jvm的pid(进程id),远程连接到jvm上

      代理类注入操作只是它众多功能中的一个,通过loadAgent方法向jvm注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例,该实例可以 在class加载前改变class的字节码,也可以在class加载后重新加载。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理。

    • VirtualMachineDescriptor 则是一个描述虚拟机的容器类,配合 VirtualMachine 类完成各种功能

    具体实现过程:通过VirtualMachine类的attach(pid)方法,便可以attach到一个运行中的java进程上,之后便可以通过loadAgent(agentJarPath)来将agent的jar包注入到对应的进程,然后对应的进程会调用agentmain方法。

    从jdk根目录的lib/tools.jar源码一路追踪了一下VirtualMachine.attach方法,发现传入一个pid之后,在Windows下会通过WindowsVirtualMachine这个类的构造函数,调用native方法,实现对jvm进程的attach

    启动一个长时间运行的jvm

    底层方法还得靠c/c++来实现(滑稽),了解这个机制之后,回到前面的agentmain的实现流程。首先启动一个一直运行的jvm

    package com.bitterz;
    
    public class Main {
        public static void main(String[] args) throws InterruptedException {
            System.out.println("Main.main() in test project start!!");
            Thread.sleep(300000000);
            System.out.println("Main.main() in test project end!!");
        }
    }
    

    打包一个agentmain代理jar

    启动之后,把再写一个agentmain,并且还要写好MANIFEST.MF文件配置,代码和pom.xml如下:

    package com.bitterz;
    
    import java.lang.instrument.Instrumentation;
    
    public class AgentMain {
        public static void agentmain(String agentArgs, Instrumentation instrumentation) {
            System.out.println("agentmain start!");
            System.out.println(instrumentation.toString());
        }
    }
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <Agent-Class>com.bitterz.AgentMain</Agent-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
    

    使用maven package打包成一个jar文件即可

    运用attach

    再开启动另一个java程序,使用进程号attach到前面一直运行的jvm,并使用loadAgent给这个jvm添加代理jar:

    package com.bitterz.attach;
    
    import com.sun.tools.attach.AgentInitializationException;
    import com.sun.tools.attach.AgentLoadException;
    import com.sun.tools.attach.AttachNotSupportedException;
    import com.sun.tools.attach.VirtualMachine;
    
    import java.io.IOException;
    
    public class AttachTest {
        public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
            VirtualMachine attach = VirtualMachine.attach("12244");  // 命令行找到这个jvm的进程号
            attach.loadAgent("C:\Users\helloworld\Desktop\java learn\java-attach\target\java-attach-1.0-SNAPSHOT.jar");
            attach.detach();
        }
    }
    

    运行这个java文件,会看到前面一直sleep运行的jvm会输出agentmain里面给定的输出!

    这里也就说明,通过attach机制,我们可以对指定运行中的jvm添加agent,而在premain方法中获取到Instrumentation对象,通过对Instrumentation对象添加transformer类,可以实现类转换(Class Transform),也就是在transform函数中结合修改字节码的方法(ASM、Javassist、cglib等)可以进一步实现RASP!

    后续的文章将会写一写如何实现这些对指定类方法的Hook,以及如何运行时对底层函数的参数进行过滤。

    参考

    https://www.cnblogs.com/rickiyang/p/11368932.html

    https://www.cnblogs.com/kendoziyu/p/maven-auto-build-javaagent-jar.html

    Java底层防护 - OpenRASP核心源码浅析


    作者:bitterz
    本文版权归作者和博客园所有,欢迎转载,转载请标明出处。
    如果您觉得本篇博文对您有所收获,请点击右下角的 [推荐],谢谢!
  • 相关阅读:
    消息分发
    subprocess.CalledProcessError: Command ‘(‘lsb_release’, ‘-a’)’ returned non-zero exit status 1.
    ubuntu14.04 安装python3.7
    通过shell脚本查看python版本并比较
    shell 小数比较大小
    python 私有方法
    python 类的倒入
    python 路径拼接
    python 脚本接受参数
    python 获取文件运行路径
  • 原文地址:https://www.cnblogs.com/bitterz/p/15080403.html
Copyright © 2020-2023  润新知