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核心源码浅析

从零开始的Java RASP实现(一)的更多相关文章

  1. 从零开始的Java RASP实现(二)

    目录 2 RASP-demo 2.1 类加载机制 双亲委派 BootStrap ClassLoader 2.2 Instrumentation介绍 Instrumentation类中常用方法 Inst ...

  2. 从零开始学 Java - Spring 集成 Memcached 缓存配置(二)

    Memcached 客户端选择 上一篇文章 从零开始学 Java - Spring 集成 Memcached 缓存配置(一)中我们讲到这篇要谈客户端的选择,在 Java 中一般常用的有三个: Memc ...

  3. 从零开始学 Java - Spring 集成 ActiveMQ 配置(一)

    你家小区下面有没有快递柜 近两年来,我们收取快递的方式好像变了,变得我们其实并不需要见到快递小哥也能拿到自己的快递了.对,我说的就是类似快递柜.菜鸟驿站这类的代收点的出现,把我们原来快递小哥必须拿着快 ...

  4. 从零开始学 Java - Spring 集成 Memcached 缓存配置(一)

    硬盘和内存的作用是什么 硬盘的作用毫无疑问我们大家都清楚,不就是用来存储数据文件的么?如照片.视频.各种文档或等等,肯定也有你喜欢的某位岛国老师的动作片,这个时候无论我们电脑是否关机重启它们永远在那里 ...

  5. 从零开始学 Java - 我放弃了 .NET ?

    这不是一篇引起战争的文章 毫无疑问,我之前是一名在微软温暖怀抱下干了近三年的 .NET 开发者,为什么要牛(sha)X一样去搞 Java 呢?因为我喜欢 iOS 阿!哈哈,开个玩笑.其实,开始学 Ja ...

  6. 从零开始学 Java - Spring 集成 ActiveMQ 配置(二)

    从上一篇开始说起 上一篇从零开始学 Java - Spring 集成 ActiveMQ 配置(一)文章中讲了我关于消息队列的思考过程,现在这一篇会讲到 ActivMQ 与 Spring 框架的整合配置 ...

  7. 从零开始学 Java - 利用 Nginx 负载均衡实现 Web 服务器更新不影响访问

    还记得那些美妙的夜晚吗 你洗洗打算看一个小电影就睡了,这个时候突然想起来今天晚上是服务器更新的日子,你要在凌晨时分去把最新的代码更新到服务器,以保证明天大家一觉醒来打开网站,发现昨天的 Bug 都不见 ...

  8. 从零开始学 Java - log4j 项目中的详细配置

    你还会用笔来写字么 我是不怎么会了,有时候老是拿起笔之后不知道这个字怎么写,这时候就会拿起手机去打出来:有时候还会写出来这个字之后越看越不像,这时候就开始怀疑自己的能力了:有时候写出来了一大堆字之后, ...

  9. 从零开始学 Java - Spring 支持 CORS 请求踩的坑

    谁没掉进过几个大坑 记得好久之前,总能时不时在某个地方看到一些标语,往往都是上面一个伟人的头像,然后不管是不是他说的话,下面总是有看起来很政治正确且没卵用的屁话,我活到目前为止,最令我笑的肚子痛得是下 ...

随机推荐

  1. 一款基于SpringBoot+SpringSecurity的后台管理系统,强烈推荐

    简介 Base Admin一套简单通用的后台管理系统,主要功能有:权限管理.菜单管理.用户管理,系统设置.实时日志,API加密,以及登录用户修改密码.配置个性菜单等. 技术栈 前端:Layui 后端: ...

  2. noip2006总结

    T1 能量项链 原题 在Mars星球上,每个Mars人都随身佩带着一串能量项链.在项链上有N颗能量珠.能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数.并且,对于相邻的两颗珠子,前一颗珠子 ...

  3. Linux主机解析顺序

    1.介绍 本篇文章由于因公司项目上线,需要对项目环境进行压力测试.在压测过程中,所有打压机从公网对目标项目服务器进行压力测试,发现和内网压力测试的性能结果差距10倍左右,在调整主机对DNS的解析顺序之 ...

  4. hive学习笔记之七:内置函数

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. 一、.Net Core 依赖注入详解及Autofac使用

    .NET中的依赖注入实际上帮助我们解耦了我们的代码,是控制反转和依赖反转原则的具体实现. .Net Core的依赖注入的好处: 1. application 更稳定,容易维护和演化: 2. 实现细节的 ...

  6. QL Server 创建用户时报错:15023 用户,组或角色'XXX'在当前数据库中已存在?

    在使用SQL Server 2000时,我们经常会遇到一个情况:需要把一台服务器上的数据库转移到另外一台服务器上.而转移完成后,需要给一个"登录"关联一个"用户" ...

  7. 自然语言处理(NLP)——简介

    自然语言处理(NLP Natural Language Processing)是一种专业分析人类语言的人工智能.就是在机器语⾔和⼈类语言之间沟通的桥梁,以实现人机交流的目的. 在人工智能出现之前,机器 ...

  8. 最新的.NET 热重载介绍

    今天,我们很高兴的向您介绍 Visual Studio 2019 版本 16.11(预览版 1)和 .NET 6 中的 dotnet watch 命令行工具(预览版 4)中的 .NET 热重载体验的可 ...

  9. tf-gpu报错:ImportError: libcublas.so.10.0: cannot open shared object file: No such file or directory

    错误1:ImportError: libcublas.so.10.0: cannot open shared object file: No such file or directory 一般这种问题 ...

  10. Python之面向对象编程【小明跑步】、【置办家具】

    #!usr/bin/python 2 #encoding=utf-8 3 #-----------------小明跑步------------- 4 #1.小明体重75.0公斤 5 #2.小明每次跑步 ...