本文属于Java ASM系列三:Tree API当中的一篇。

1. Core Based Class Transformation

在Core API当中,使用ClassReaderClassVisitorClassWriter类来进行Class Transformation操作的整体思路是这样的:

ClassReader --> ClassVisitor(1) --> ... --> ClassVisitor(N) --> ClassWriter

在这些类当中,它们有各自的职责:

  • ClassReader类负责“读”Class。
  • ClassWriter类负责“写”Class。
  • ClassVisitor类负责进行“转换”(Transformation)。

因此,我们可以说,ClassVisitor类是Class Transformation的核心操作。

2. Class Transformation的本质

对于Class Transformation来说,它的本质就是“中间人攻击”(Man-in-the-middle attack)。

Wiki当中,是这样描述Man-in-the-middle attack的:

In cryptography and computer security, a man-in-the-middle(MITM) attack is a cyberattack where the attacker secretly relays and possibly alters the communications between two parties who believe that they are directly communicating with each other.

Man-in-the-middle attack

3. Tree Based Class Transformation

首先,思考一个问题:基于Tree API的Class Transformation要怎么进行呢?它是要完全开发一套全新的处理流程,还是利用已有的Core API的Class Transformation流程呢?

回答:要实现Tree API的Class Transformation,Java ASM利用了已有的Core API的Class Transformation流程。

ClassReader --> ClassVisitor(1) --> ... --> ClassNode(M) --> ... --> ClassVisitor(N) --> ClassWriter

因为ClassNode类(Tree API)是继承自ClassVisitor类(Core API),因此这里的处理流程和上面的处理流程本质上一样的。

虽然处理流程本质上是一样的,但是还有三个具体的技术细节需要处理:

  • 第一个,如何将Core API(ClassReaderClassVisitor)转换成Tree API(ClassNode)。
  • 第二个,如何将Tree API(ClassNode)转换成Core API(ClassVisitorClassWriter)。
  • 第三个,如何对ClassNode进行转换。

3.1. 从Core API到Tree API

从Core API到Tree API的转换,有两种情况。

第一种情况,将ClassReader类转换成ClassNode类,要依赖于ClassReaderaccept(ClassVisitor)方法:

ClassNode cn = new ClassNode(Opcodes.ASM9);

ClassReader cr = new ClassReader(bytes);
int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cn, parsingOptions);

第二种情况,将ClassVisitor类转换成ClassNode类,要依赖于ClassVisitor的构造方法:

int api = Opcodes.ASM9;
ClassNode cn = new ClassNode();
ClassVisitor cv = new XxxClassVisitor(api, cn);

3.2. 从Tree API到Core API

从Tree API到Core API的转换,要依赖于ClassNodeaccept(ClassVisitor)方法:

ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cn.accept(cw);

3.3. 如何对ClassNode进行转换

ClassNode对象实例进行转换,其实就是对其字段的值进行修改。

3.3.1. 第一个版本

首先,我们来看第一个版本,就是在拿到ClassNode cn之后,直接对cn里的字段值进行修改。

import lsieun.utils.FileUtils;
import org.objectweb.asm.*;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);
        byte[] bytes1 = FileUtils.readBytes(filepath);
        if (bytes1 == null) {
            throw new RuntimeException("bytes1 is null");
        }

        // (1)构建ClassReader
        ClassReader cr = new ClassReader(bytes1);

        // (2) 构建ClassNode
        int api = Opcodes.ASM9;
        ClassNode cn = new ClassNode(api);
        cr.accept(cn, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);

        // (3) 进行transform
        cn.interfaces.add("java/lang/Cloneable");

        // (4) 构建ClassWriter
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        cn.accept(cw);

        // (5) 生成byte[]内容输出
        byte[] bytes2 = cw.toByteArray();
        FileUtils.writeBytes(filepath, bytes2);
    }
}

这个版本有点“鲁莽”和“原始”,如果进行变换的内容复杂,代码就会变得很“臃肿”,缺少一点面向对象的“美学”。那么,怎么改进呢?

3.3.2. 第二个版本

在第二个版本中,我们就引入一个ClassTransformer类,它的作用就是将需要进行的变换封装成一个类。

import org.objectweb.asm.tree.ClassNode;

public class ClassTransformer {
    protected ClassTransformer ct;

    public ClassTransformer(ClassTransformer ct) {
        this.ct = ct;
    }

    public void transform(ClassNode cn) {
        if (ct != null) {
            ct.transform(cn);
        }
    }
}

Transforming a class with the tree API can be done by putting these elements together:

// (1)构建ClassReader
ClassReader cr = new ClassReader(bytes1);

// (2) 构建ClassNode
int api = Opcodes.ASM9;
ClassNode cn = new ClassNode(api);
cr.accept(cn, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);

// (3) 进行transform
ClassTransformer ct = ...;
ct.transform(cn);

// (4) 构建ClassWriter
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cn.accept(cw);

// (5) 生成byte[]内容输出
byte[] bytes2 = cw.toByteArray();

代码示例:

import lsieun.asm.tree.transformer.ClassAddFieldTransformer;
import lsieun.asm.tree.transformer.ClassAddMethodTransformer;
import lsieun.asm.tree.transformer.ClassTransformer;
import lsieun.utils.FileUtils;
import org.objectweb.asm.*;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodNode;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);
        byte[] bytes1 = FileUtils.readBytes(filepath);
        if (bytes1 == null) {
            throw new RuntimeException("bytes1 is null");
        }

        // (1)构建ClassReader
        ClassReader cr = new ClassReader(bytes1);

        // (2) 构建ClassNode
        int api = Opcodes.ASM9;
        ClassNode cn = new ClassNode(api);
        cr.accept(cn, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);

        // (3) 进行transform
        ClassTransformer ct1 = new ClassAddFieldTransformer(null, Opcodes.ACC_PUBLIC, "intValue", "I");
        ClassTransformer ct2 = new ClassAddMethodTransformer(ct1, Opcodes.ACC_PUBLIC, "abc", "()V") {
            @Override
            protected void generateMethodBody(MethodNode mn) {
                InsnList il = mn.instructions;
                il.add(new InsnNode(Opcodes.RETURN));
                mn.maxStack = 0;
                mn.maxLocals = 1;
            }
        };
        ct2.transform(cn);

        // (4) 构建ClassWriter
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        cn.accept(cw);

        // (5) 生成byte[]内容输出
        byte[] bytes2 = cw.toByteArray();
        FileUtils.writeBytes(filepath, bytes2);
    }
}

第二个版本,其实挺好的,但是仍然有改进的余地,就是“学会内敛”。“学会内敛”是什么意思呢?我们结合生活当中的例子来说一下,有一句话叫“腹有诗书气自华”。如果你有才华,但到处卖弄,会非常招人讨厌;但如果你将才华藏于自身,不轻易示人,这样你的才华就会体现你的气质中,也会招人喜欢。

4. Two Common Patterns

It is possible to use a tree based class transformer like a class adapter with the core API.
Two common patterns are used for that.

4.1. First Pattern

The first one uses inheritance:

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.tree.ClassNode;

public class MyClassNode extends ClassNode {
    public MyClassNode(int api, ClassVisitor cv) {
        super(api);
        this.cv = cv;
    }

    @Override
    public void visitEnd() {
        // 首先,处理自己的代码逻辑
        // put your transformation code here
        // 使用ClassTransformer进行转换

        // 其次,调用父类的方法实现(根据实际情况,选择保留,或删除)
        super.visitEnd();

        // 最后,向后续ClassVisitor传递
        if (cv != null) {
            accept(cv);
        }
    }
}

那么,可以对MyClassNode按如下思路进行使用:

// (1)构建ClassReader
ClassReader cr = new ClassReader(bytes1);

// (2)构建ClassWriter
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

// (3)串连ClassNode
int api = Opcodes.ASM9;
ClassNode cn = new MyClassNode(api, cw);

//(4)结合ClassReader和ClassNode
int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cn, parsingOptions);

// (5) 生成byte[]
byte[] bytes2 = cw.toByteArray();

4.2. Second Pattern

The second pattern uses delegation instead of inheritance:

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.tree.ClassNode;

public class MyClassVisitor extends ClassVisitor {
    private final ClassVisitor next;

    public MyClassVisitor(int api, ClassVisitor classVisitor) {
        super(api, new ClassNode());
        this.next = classVisitor;
    }

    @Override
    public void visitEnd() {
        // 首先,处理自己的代码逻辑
        ClassNode cn = (ClassNode) cv;
        // put your transformation code here
        // 使用ClassTransformer进行转换

        // 其次,调用父类的方法实现(根据实际情况,选择保留,或删除)
        super.visitEnd();

        // 最后,向后续ClassVisitor传递
        if (next != null) {
            cn.accept(next);
        }
    }
}

那么,可以对MyClassVisitor按如下思路进行使用:

//(1)构建ClassReader
ClassReader cr = new ClassReader(bytes1);

//(2)构建ClassWriter
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

//(3)串连ClassVisitor
int api = Opcodes.ASM9;
ClassVisitor cv = new MyClassVisitor(api, cw);

//(4)结合ClassReader和ClassVisitor
int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cv, parsingOptions);

//(5)生成byte[]
byte[] bytes2 = cw.toByteArray();

5. 总结

本文内容总结如下:

  • 第一点,介绍Core Based Class Transformation的处理流程是什么。
  • 第二点,Class Transformation的本质是什么。(中间人攻击)
  • 第三点,Tree Based Class Transformation是利用了已有的Core API处理流程,在过程中需要解决三个技术细节问题:
    • 第一个问题,如何将Core API转换成Tree API
    • 第二个问题,如何将Tree API转换成Core API
    • 第三个问题,如何对ClassNode类进行转换
  • 第四点,使用Tree API进行Class Transformation的两种Pattern是什么。

在刚接触Tree Based Class Transformation的时候,可能不知道如何开始着手,我们可以按下面的步骤来进行思考:

  • 第一步,读取具体的.class文件,是使用ClassReader类,它属于Core API的内容。
  • 第二步,思考如何将Core API转换成Tree API。
  • 第三步,思考如何使用Tree API进行Class Transformation操作。
  • 第四步,思考如何将Tree API转换成Core API。
  • 第五步,最后落实到ClassWriter类,调用其toByteArray()方法来生成byte[]内容。