1
0x00 前言
本来想学RASP,但是学了一会发现好像很迷,便先打一下基础。参考文章如下:
0x01 访问者模式
RASP 通常依赖 Java Instrumentation 机制对目标类进行字节码修改。在具体实现上,许多 RASP 方案会使用 ASM、ByteBuddy 或 Javassist 等字节码操作库。其中的 ASM 本身基于访问者模式实现对 Java 字节码的解析和修改。
先假设一个情境,有A和B两个对象,假如都需要给它们添加一个方法叫lookup()。一般来说,我们会把方法直接写到类里。但是如果该方法是比较通用的方法,或者是需要频繁更改的方法,就会极大的提高工作量。
但是如果该方法的实现交给访问者(Visitor)类,A对象只需要实现accept方法,其传入的参数为visitor接口,函数体为调用接口中定义有关于A的方法,即lookA,这样跟lookup方法有关的逻辑就都写到Visitor实现类,之后A对象调用accept(visitor)方法即可。
简单来说,访问者模式将对目标对象结构的操作从对象本身分离出来,使得操作(访问逻辑)与目标类的结构解耦。
为什么RASP会用到访问者模式呢,不妨想象,当JVM从读取各种类字节码中提取各种信息或者采取对应的各种操作的时候,是不是就像AB类都需要添加lookup方法一样,是一个通用的操作,这种通用的操作如果写在每个类里是非常不现实的事情。但是如果在读取类的时候,用一个访问者去实现这些需求,是不是就把各种类的字节码结构与前面的操作解耦了,工程量也少了不少。
我们前面学过Java Agent,其有两种模式:
- premain:允许在main开始前修改字节码,也就是在大部分类加载前对字节码进行修改。
- agentmain:允许在main执行后通过
com.sun.tools.attach的Attach API attach到程序运行时中,通过retransform的方式修改字节码,也就是在类加载后通过类重新转换(定义)的方式在方法体中对字节码进行修改,其本质还是在类加载前对字节码进行修改。
当然本文的重点还是premain。
0x02 ASM
ClassVisitor
ASM 是一个通用的 Java 字节码操作和分析框架。它可直接以二进制形式用于修改现有类或动态生成类。应用场景有代码转换、优化、代码生成、动态字节码增强等。可以概括为generation、transformation 和 analysis
ASM 的类生成与转换 API 基于 ClassVisitor 抽象类,它采用装饰器式的链式结构。每个 ClassVisitor 接收到的 visitXXX 事件都会被按需处理,然后委托给下一个 ClassVisitor,从而形成可组合的处理管道。
先看一下 ClassVisitor 抽象类的类结构:

ASM 在解析一个类时,会按照固定的顺序把类的各部分依次回调给 ClassVisitor:
- 最先调用
visit()—— 类的基本信息 - 接着是可选的
visitSource()和visitOuterClass() - 然后是一系列类级别的注解与属性
- 再之后依次回调内部类、字段、方法等结构
- 最后调用
visitEnd()表示类访问结束
这个顺序定义了 ASM 对 class 文件的访问流程。
ClassReader
ClassVisitor 本身不会解析 class 文件,其只是一个“事件接收器”。真正负责读取并解析字节码的是 ClassReader。ClassReader 负责把字节码解析成 visitXXX 事件,ClassVisitor负责处理这些事件。所以我们能得到如下的流程:
1 | ClassReader(解析 class) → 按顺序触发 visitXXX 事件 → ClassVisitor 链处理这些事件 |
ClassWriter
ClassWriter 作为 ClassVisitor 抽象类的一个子类,它接收这些访问事件并根据这些事件构建新的字节码,最终通过 toByteArray()方法输出结果。
0x03 小结
其实ClassVisitor里有很多重要的方法,等到下一篇文章再讲。