本文为JAVA安全系列文章第二十五篇,前面已经学过了javassist字节码操作库,本文我们来认识另一个字节码操作框架—ASM,主要理解掌握ASM的设计原理。
0x01 ASM框架
一、ASM介绍
根据ASM官网https://asm.ow2.io的介绍:
ASM 是一个通用的 Java 字节码操作和分析框架。它可用于直接以二进制形式修改现有类或动态生成类。
ASM提供了一些常见的字节码转换和分析算法,可以从中构建自定义的复杂转换和代码分析工具。
ASM 提供与其他 Java 字节码框架类似的功能,但更注重性能。因为它被设计和实现为尽可能小和快,所以它非常适合在动态系统中使用(但当然也可以以静态方式使用,例如在编译器中)。
注:ASM 的名字没有任何含义,它只是引用 C 语言中的_asm_关键字,这个关键字允许执行一些用汇编语言编写的函数。
二、ASM API
ASM 库提供了两种用于生成和转换已编译类的 API,一种是Core API,以基于事件的形式来表示类;另一个是Tree API,以基于对象的形式来表示类。
ASM Core API可以类比解析XML文件中的SAX方式,不需要把这个类的整个结构读取进来,就可以用流式的方法来处理字节码文件。好处是非常节约内存,但是编程难度较大。然而出于性能考虑,一般情况下编程都使用Core API。本文仅学习Core API。
ASM Tree API可以类比解析XML文件中的DOM方式,把整个类的结构读取到内存中,缺点是消耗内存多,但是编程比较简单。TreeAPI通过各种Node类来映射字节码的各个区域,类比DOM节点。
三、组织形式
ASM 库组织在几个包中,这些包分布在几个 jar 文件中:
org.objectweb.asm 和org.objectweb.asm.signature 包定义基于事件的API 并提供类解析器和编写器组件。它们包含在 asm.jar中。本文学习的类也主要位于这个包中。
org.objectweb.asm.util 包,在 asm-util.jar 中,提供基于核心API的各种工具,可以在ASM应用程序的开发和调试过程中使用。
org.objectweb.asm.commons 包提供了几个有用的预定义类转换器,主要基于核心API。它包含在 asm-commons.jar 中。
org.objectweb.asm.tree 包,在asm-tree.jar 中, 定义了基于对象的 API,并提供了在基于事件和基于对象的表示之间进行转换的工具。
org.objectweb.asm.tree.analysis 包提供了一个类分析框架和几个基于树API 的预定义类分析器。它包含在 asm-analysis.jar 中。
各个包中的类和API详情请看Javadoc文档:https://asm.ow2.io/javadoc/index.html
0x02 ASM的设计原理
一、什么是访问者设计模式
ASM对字节码的操作和分析都是基于访问者模式来实现的。因此想要理解并使用ASM也得了解什么是访问者设计模式,这里简单做个介绍:
访问者是一种行为设计模式,允许在不修改已有代码的情况下向已有类层次结构中增加新的行为。
访问者模式建议将新行为放入一个名为访问者的独立类中, 而不是试图将其整合到已有类中。需要执行操作的原始对象将作为参数被传递给访问者中的方法, 让方法能访问对象所包含的一切必要数据。
二、访问者设计模式的实现方式
1.声明一个访问者接口。在访问者接口中声明一组 “访问” 方法, 分别对应程序中的每个具体元素类。
2.声明元素接口。如果程序中已有元素类层次接口,可在层次结构基类中添加抽象的 “接收” 方法。该方法必须接受访问者对象作为参数。
3.在所有具体元素类中实现接收方法。这些方法必须将调用重定向到当前元素对应的访问者对象中的访问者方法上(对应上面说的“需要执行操作的原始对象将作为参数被传递给访问者中的方法”,即元素与访问者对象相互引用)。
4.元素类只能通过访问者接口与访问者进行交互。不过访问者必须知晓所有的具体元素类, 因为这些类在访问者方法中都被作为参数类型引用。
5.为每个无法在元素层次结构中实现的行为创建一个具体访问者类并实现所有的访问者方法。
可能会遇到访问者需要访问元素类的部分私有成员变量的情况。
在这种情况下,要么将这些变量或方法设为公有,这将破坏元素的封装;要么将访问者类嵌入到元素类中。
后一种方式只有在支持嵌套类的编程语言中才可能实现。6.客户端必须创建访问者对象并通过 “接收” 方法将其传递给元素。
访问者模式结构图:
上面的概念或许有些抽象,想要更理解访问者设计模式请参考:https://refactoringguru.cn/design-patterns/visitor,里面举了一个很详细的例子。
需要对一个复杂对象结构 (例如字节码结构)中的所有元素(例如字节码结构中的字段表,方法表等)执行某些操作(例如修改字段的属性,方法的代码等),可使用访问者模式。
那么,访问者设计模式的这种思想是如何引用到ASM的设计中的呢?
三、浅谈ASM的设计实现
注:本文讨论的仅仅是Core API(核心API)。
ASM的设计并没有完全照搬上面的访问者模式结构图,因为ASM中并没有定义所谓的访问者接口和元素接口。不过其核心思想与访问者模式是一样的。
首先,我们的复杂对象结构即字节码结构它是一个二进制流的数据,因此我们需要写一个类来解析它为一个对象或者说将它转换为我们熟悉的Java对象,于是ASM的设计者就写了一个ClassReader类,它是字节码文件结构的解析器,遵循JAVA虚拟机规范,读取字节码文件并将内容解析为我们所知道的字段、方法和字节码指令等,此处可把ClassReader看成是访问者模式结构图中的具体元素的合集,则ClassReader必然会有一个accept()方法,且accept()必然有一个参数为访问者对象(为了将调用重定向到当前元素对应的访问者对象中的访问者方法上):
public class ClassReader {
public ClassReader(final byte[] classFile)
public ClassReader(final InputStream inputStream)
public ClassReader(final String className)
public void accept(final ClassVisitor classVisitor, final int parsingOptions)
private int readField(
final ClassVisitor classVisitor, final Context context, final int fieldInfoOffset)
private int readMethod(
final ClassVisitor classVisitor, final Context context, final int methodInfoOffset)
...
}其次,ASM虽然没有定义访问者接口,但提供了一个抽象的访问者类ClassVistor,类中的每个方法都对应于同名的类文件结构部分:
public abstract class ClassVisitor {
public ClassVisitor(final int api)
public ClassVisitor(final int api, final ClassVisitor classVisitor)
public void visit(final int version,final int access,final String name,
final String signature,final String superName,final String[] interfaces)
public void visitSource(final String source, final String debug)
public void visitOuterClass(final String owner, final String name, final String descriptor)
public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible)
public void visitAttribute(final Attribute attribute)
public void visitInnerClass(final String name, final String outerName, final String innerName, final int access)
public FieldVisitor visitField(final int access,final String name,final String descriptor,final String signature,final Object value)
public MethodVisitor visitMethod(final int access,final String name,final String descriptor,final String signature,final String[] exceptions)
public void visitEnd()
}简单的部分只需一个方法调用就能访问,这个调用返回 void,其参数描述了这些部分的内容。
有些部分的内容可以达到任意长度、 任意复杂度,这样的部分可以用一个初始方法调用来访问,返回一个辅助的访问者类。比如:visitAnnotation、visitField 和 visitMethod 方法就是这种情况,它们分别返回AnnotationVisitor、FieldVisitor 和 MethodVisitor。
针对这些辅助类递归适用同样的原则。例如,FieldVisitor抽象类中的每个方法对应于同名的类文件子结构,visitAnnotation返回一个辅助的 AnnotationVisitor, 和在 ClassVisitor 中一样。
public abstract class FieldVisitor {
public FieldVisitor(final int api)
public FieldVisitor(final int api, final FieldVisitor fieldVisitor)
public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible)
public void visitAttribute(final Attribute attribute)
public void visitEnd()
}注:辅助访问者类与ClassVisitor类并无继承关系,只是它们可由ClassVisitor中的对应方法获得,都是抽象类,使用者可以写个子类继承它们并重写其中的方法,以达到对字节码这个复杂对象结构中的字段、方法等元素执行某些操作的目的(比如增/删字段,增/删方法,修改方法代码等)。
最后,我们可能需要将修改后的字节码写到磁盘上,ASM提供了ClassWriter类,它继承自ClassVisitor,且重写了ClassVisitor中的各个visitXxx方法,它的toByteArray方法可将字节码转化为对应的字节数组,从而通过文件输出流将修改后的字节码写到磁盘文件中。
public class ClassWriter extends ClassVisitor {
public ClassWriter(final int flags)
public ClassWriter(final ClassReader classReader, final int flags)
@Override
visitXxx()
public byte[] toByteArray()
}四、ASM操作流程图
先上一张简单明了的ASM操作流程图:
前面说ASM的Core API是基于事件的,下面就围绕事件来对图中的过程进行说明:
1.ClassReader:用于读取已经编译好的.class文件
该类分析以字节数组形式给出的已编译类,并针对在其accept 方法参数中传送的ClassVisitor 实例,调用相应的 visitXxx 方法。这个类可以看作一个事件产生器。
2.ClassWriter :用于重新构建编译后的类,如修改类名、属性以及方法,也可以生成新的类的字节码文件
该类是ClassVisitor抽象类的一个子类,它直接以二进制形式生成编译后的类。它会生成一个字节数组形式的输出,其中包含了已编译类,可以用 toByteArray 方法来提取。这个类可以看作一个事件使用器。
3.各种Visitor类:对字节码执行操作的逻辑都写在对应Visitor类的方法中了。
有对应于整个字节码文件的ClassVisitor,以及对应于字节码文件中不同区域的各个辅助Visitor,比如用于访问方法的MethodVisitor、用于访问类变量的FieldVisitor、用于访问注解的AnnotationVisitor等。我们实际常用的是ClassVisitor和MethodVisitor。
0x03 总结
一、本文重点应理解掌握访问者设计模式以及ASM Core API的设计原理,理解原理是为了能够更好地应用。
二、建议阅读Core API源码,尤其是其中几个重要关键类:ClassReader、ClassWriter、ClassVisitor、MethodVisitor、FieldVisitor等。这样才能更好地知道如何使用ASM,下一篇我们就从实践中来学习ASM的基本使用。
参考:
访问者设计模式:https://refactoringguru.cn/design-patterns/visitor
《ASM4-guide》:https://asm.ow2.io/asm4-guide.pdf
Java安全系列文集
第6篇:JAVA安全|基础篇:反射机制之常见ReflectionAPI使用
第8篇:JAVA安全|Gadget篇:TransformedMap CC1链
第10篇:JAVA安全|Gadget篇:LazyMap CC1链
第11篇:JAVA安全|Gadget篇:无JDK版本限制的CC6链
第14篇:JAVA安全|Gadget篇:CC3链及其通杀改造
第15篇:JAVA安全|Gadget篇:CC依赖下为shiro反序列化利用而生的CCK1 CC11链
第17篇:JAVA安全|Gadget篇:CC2 CC4链—Commons-Collections4.0下的特有链
第19篇:JAVA安全|Gadget篇:Ysoserial CB1链
第20篇:JAVA安全|Gadget篇:shiro无依赖利用与常见shiro利用工具对比浅析
第21篇:JAVA安全|Gadget篇:JDK原生链—JDK7u21
第22篇:JAVA安全|字节码篇:字节码操作库—javassist
第24篇:JAVA安全|字节码篇:常见字节码指令(JVM指令)
如果喜欢小编的文章,记得多多转发,点赞+关注支持一下哦~,您的点赞和支持是我最大的动力~