Android字节码处理-使用AsmClassVisitorFactory插桩

728 阅读4分钟

Java项目中ASM字节码处理

增加asm的依赖关系

implementation("org.ow2.asm:asm:9.4")
implementation("org.ow2.asm:asm-tree:9.4")

Comparable的原始接口如下面代码所示:

public interface Comparable {
    int LESS = -1;
    int EQUAL = 0;
    int GREATER = 1;
    int compareTo(Object o);
}

RemoveMethodAdapter的代码如下所示:

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;

import static org.objectweb.asm.Opcodes.ASM4;

public class RemoveMethodAdapter extends ClassVisitor {
    private String mName;
    private String mDesc;
    public RemoveMethodAdapter(
            ClassVisitor cv, String mName, String mDesc) {
        super(ASM4, cv);
        this.mName = mName;
        this.mDesc = mDesc;
    }
    @Override
    public MethodVisitor visitMethod(int access, String name,
                                     String desc, String signature, String[] exceptions) {
        if (name.equals(mName) && desc.equals(mDesc)) {
// do not delegate to next visitor -> this removes the method
            System.out.println("remove method name is  " + mName + " desc is " + mDesc);
            return null;
        }
        return cv.visitMethod(access, name, desc, signature, exceptions);
    }
}

ChangeFieldVisitor的代码如下所示:

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;

import static org.objectweb.asm.Opcodes.ASM4;

public class ChangeFieldVisitor extends ClassVisitor {
    private String name;
    private Object value;
    protected ChangeFieldVisitor(ClassVisitor cv, String name, Object value) {
        super(ASM4, cv);
        this.name = name;
        this.value = value;
    }

    @Override
    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
        if (name.equals(this.name)) {
// do not delegate to next visitor -> this removes the method

            System.out.println("change field visitor is  " + this.name + " value is " + this.value);
            return super.visitField(access, name, descriptor, signature, this.value);
        }
        return super.visitField(access, name, descriptor, signature, value);
    }
}

ComparableTest的代码如下:

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

import java.io.*;

public class ComparableTest {
    public static void main(String[] args) throws IOException {

        // Input class file
        File inputFile = new File("build/classes/java/main/Comparable.class");

        ClassReader cr = null;
        try {
            FileInputStream fis = new FileInputStream(inputFile);
            cr = new ClassReader(fis);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        ClassWriter cw = new ClassWriter(0);
        RemoveMethodAdapter ca = new RemoveMethodAdapter(cw, "compareTo", "(Ljava/lang/Object;)I");
        ChangeFieldVisitor changeFieldVisitor = new ChangeFieldVisitor(ca, "LESS", 2);
        cr.accept(changeFieldVisitor, 0);

        // Write the modified class to a new file
        File outputFile = new File("build/classes/java/main/Comparable.class");
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(outputFile);
            fos.write(cw.toByteArray());
            fos.close();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }


        System.out.println("Class modified and saved to: " + outputFile.getAbsolutePath());
//        Comparable comparable = new Comparable() {
//            @Override
//            public int compareTo(Object o) {
//                return 0;
//            }
//        };
        System.out.print("Comparable.LESS is " + Comparable.LESS);
    }

    private static String getClassPath(Class<?> clazz) {
        System.out.println("clazz path is " + clazz.getName().replace('.', '/') + ".class");
        return clazz.getName().replace('.', '/') + ".class";
    }
}

使用ASM可以对.class字节码增加属性,删除属性,修改属性,增加方法,删除方法,修改方法等逻辑操作。ClassReader基于事件驱动,ASM使用访问者设计模式,ClassVisitor可以链式链接,这样就能依次实现相关的业务逻辑。

代码的执行结果如下:

> Task :ComparableTest.main()
change field visitor is  LESS value is 2
Class modified and saved to: /Users/qingfeng/IdeaProjects/KotlinFirst/build/classes/java/main/Comparable.class
Comparable.LESS is 2
BUILD SUCCESSFUL in 336ms
3 actionable tasks: 1 executed, 2 up-to-date
14:24:46: Execution finished ':ComparableTest.main()'.

Android字节码处理

Android的Transform API(在Android Gradle插件(AGP)中用于在构建过程中修改类文件和字节码)已被弃用(AGP7.0+弃用了Transform API,而AGP 8.0+ 则将其完全删除。)。取而代之的是 ASM(用于 Instrumentation 的 Android Gradle 插件 API)。从Android的开发教程中得到如下信息:使用Instrumentation相对于Transfrom性能提升18%,代码编写数量是原来的五分之一左右。Transform被废弃的一个重要原因是编译过程中多次回归编译导致性能低。

androidAsmClassVisitorFactory插桩

下面相关逻辑在buildSrc模块中。

创建MyClassVisitor(用于更改字节码)

创建MyClassVisitor.java以将日志注入sayHello()方法中。

import org.objectweb.asm.*;

import static org.objectweb.asm.Opcodes.*;

public class MyClassVisitor extends ClassVisitor {
    public MyClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM9, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if ("sayHello".equals(name)) {
            return new MyMethodVisitor(mv);
        }
        return mv;
    }

    static class MyMethodVisitor extends MethodVisitor {
        public MyMethodVisitor(MethodVisitor mv) {
            super(Opcodes.ASM9, mv);
        }

        @Override
        public void visitCode() {
            super.visitCode();
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("[ASM] Entering sayHello()");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }
    }
}

如果MyClassVisitor的方法visitMethod的返回值为null,代表class中的方法被删除了。

创建MyClassVisitorFactory

重写createClassVisitor和isInstrumentable方法。

import com.android.build.api.instrumentation.*;
import org.objectweb.asm.ClassVisitor;

public abstract class MyClassVisitorFactory implements AsmClassVisitorFactory<InstrumentationParameters.None> {
    @Override
    public ClassVisitor createClassVisitor(ClassContext classContext, ClassVisitor nextVisitor) {
        return new MyClassVisitor(nextVisitor);
    }

    @Override
    public boolean isInstrumentable(ClassData classData) {
        return classData.getClassName().equals("com.zj.android_asm.MainActivity");
    }
}

创建MethodRecordPlugin

Plugin注册ClassVisitorFactory。

import com.android.build.api.instrumentation.FramesComputationMode
import com.android.build.api.instrumentation.InstrumentationScope
import com.android.build.api.variant.AndroidComponentsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project


class MethodRecordPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        val androidComponents = target.extensions.getByType(AndroidComponentsExtension::class.java)

        androidComponents.onVariants { variant ->
            variant.instrumentation.transformClassesWith(
                MyClassVisitorFactory::class.java,
                InstrumentationScope.ALL
            ) {}
            variant.instrumentation.setAsmFramesComputationMode(
                FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
            )
        }
    }
}

创建newmethod.properties文件

main目录下新建resources/META-INF/gradle-plugins目录,同时配置MethodRecordPlugin,内容如下:

implementation-class=com.zj.asm.methodrecord.MethodRecordPlugin

注意:以下内容都是在app模块中。

app模块中使用定义的MethodRecordPlugin

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id("newmethod") apply true
}

id("newmethod") apply true配置中newmethod就是buildSrc配置Plugin的properties文件名。

MainActivity

import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.zj.android_asm.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        setSupportActionBar(binding.toolbar)
        sayHello()
    }

    fun sayHello() {
        println("Hello, World!")
    }

    private fun test(time: Long) {
        Thread.sleep(time)
    }

    private fun sum(i: Int, j: Int): Int {
        return i + j
    }
}

最后执行结果如下图所示:

image.png 如上图代表插桩已经成功了。

总结

ASM可以做的事情很多,比如通过添加字段、添加方法和改变现有方法的行为来操作现有的Java类,从而达到改变应用行为的目的,ASM还可以做分析。希望文章对您有所帮助。

参考资料

ASM官网:asm.ow2.io/

将build配置从Groovy迁移到 Kotlin:developer.android.com/build/migra…