[Android][ASM]指令注入入门(四)——实际需求实现(中)

6 篇文章 0 订阅

[Android][ASM]指令注入入门(四)——实际需求实现(中)

前言

上一篇中,我们实现了无返回值的方法收尾插入Binder.clearCallingIdentity()/Binder.restoreCallingIdentity()方法;
而这一篇,会在此基础上,将使我们的工具支持带返回值的方法;

开发步骤

源码端


```java
import java.util.Random;

public class Test2 {
    
    private static final Random R = new Random();

    public static void main(String[] args) {
        Test2 instance = new Test2();
        instance.call(R.nextInt());
        //Add begin
        System.out.println("getInt:" + instance.getInt());
        System.out.println("getInt:" + instance.getInt(R.nextInt()));
        System.out.println("getInt2:" + instance.getInt2(R.nextInt()));
        //Add end
    }

    private void call(int arg) {
        System.out.println("call-->" + arg + 1);
    }

    //Add begin
    private int getInt() {
        return -1;
    }
    
    private int getInt(int intVal) {
        return intVal * -1;
    }
        
    private int getInt2(int intVal) {
        int ret = intVal + 1;
        return ret;
    }
    //Add end
}

这里先用返回值为int基本数据类型的来尝试,并写出了三种常见的结构:

  1. 返回一个固定值;
  2. 基于传入参数进行简单计算后返回;
  3. 基于传入参数进行复杂计算后返回,期间涉及多个变量赋值情况;

编译:

ryan ~/workspace/src $ javac -g Test2.java android/os/Binder.java

对生成的Test2.class进行反汇编:(关于javap的使用会单独写一篇介绍,在此先不展开介绍)

ryan ~/workspace/src $ javap -c -s -l -p Test2

得到如下结果(节选):

  private int getInt();
    descriptor: ()I
    Code:
       0: iconst_m1
       1: ireturn
    LineNumberTable:
      line 28: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       2     0  this   LTest2;

  private int getInt(int);
    descriptor: (I)I
    Code:
       0: iload_1
       1: iconst_m1
       2: imul
       3: ireturn
    LineNumberTable:
      line 32: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       4     0  this   LTest2;
          0       4     1 intVal   I

  private int getInt2(int);
    descriptor: (I)I
    Code:
       0: iload_1
       1: iconst_1
       2: iadd
       3: istore_2
       4: iload_2
       5: ireturn
    LineNumberTable:
      line 37: 0
      line 39: 4
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       6     0  this   LTest2;
          0       6     1 intVal   I
          4       2     2   ret   I

通过观察,可以得出上述三种方法的规律:

  1. 返回一个固定值——ireturn指令之前即为iconst_m?
  2. 基于传入参数进行简单计算后返回——ireturn指令之前会有iload_?,但两者之间存在其他运算指令;
  3. 基于传入参数进行复杂计算后返回,期间涉及多个变量赋值情况——ireturn指令之前即为iload_?

针对第一种情况,实际上是不需要处理的,但是为了展示,我们还是尝试处理下:
在不考虑效率问题的前提下,以下两个方法实现在逻辑上是等效的:

    private int getInt() {
        return -1;
    }
    private int getInt() {
        int ret = -1;
        return ret;
    }

而后者通过javap拆解字节码后得到的结果如下:

  private int getInt();
    descriptor: ()I
    Code:
       0: iconst_m1
       1: istore_1
       2: iload_1
       3: ireturn
    LineNumberTable:
      line 28: 0
      line 29: 2
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       4     0  this   LTest2;
          2       2     1   ret   I

因此可以认为,第一种情况可以通过在ireturn之前插入istore_?iload_?指令,将其转化为第三种情况;

至于第二、第三种情况,则需要分情况处理:

  1. ireturniload_?之间有间隔,则在ireturn之前插入Binder.restoreCallingIdentity(),并再次插入iload_?指令;
  2. ireturniload_?之间无间隔,则在iload_?之前插入Binder.restoreCallingIdentity()

工具端

基于上一篇的代码状态,修改visitEnd(),并添加必要的方法封装:

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.List;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;

public class MethodRegionInjector {
	//与之前StringModifier.java基本一致,仅改变输入、输出文件名
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("../src/Test2.class");
        FileOutputStream fos = new FileOutputStream("../out/Test2.class");
        convert(fis, fos);
        fos.flush();
        fis.close();
        fos.close();
    }

	//与之前StringModifier.java基本一致,仅改变visitor类型
    private static void convert(InputStream in, OutputStream out)
            throws IOException {
        ClassReader reader = new ClassReader(in);
        ClassWriter writer = new ClassWriter(0);
        MethodRegionInjectorClassVisitor visitor = new MethodRegionInjectorClassVisitor(writer);
        reader.accept(visitor, 0);
        byte[] data = writer.toByteArray();
        out.write(data);
    }

	//与之前StringModifier.StringModificationClassVisitor基本一致,仅改变visitMethod的实现
    private static class MethodRegionInjectorClassVisitor extends ClassVisitor {

        private String currentClassName = null;

        private MethodRegionInjectorClassVisitor(ClassWriter writer) {
            super(Opcodes.ASM6, writer);
        }

        @Override
        public void visit(int version, int access, String name, String signature, String superName,
                String[] interfaces) {
            currentClassName = name;
            super.visit(version, access, name, signature, superName, interfaces);
        }

        
        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature,
                String[] exceptions) {
            MethodVisitor chain = super.visitMethod(access, name, desc, signature, exceptions);
            //过滤类名、方法名,忽略构造方法、静态代码块
            if ("Test2".equals(currentClassName) && !"<init>".equals(name) && !"<clinit>".equals(name) && (access & Opcodes.ACC_STATIC) == 0) {
                MethodNode mn = new MethodNode(Opcodes.ASM6, access, name, desc, signature, exceptions);
                return new MethodRegionInjectorMethodVisitor(currentClassName, chain, mn);
            } else {
                return chain;
            }
        }

    }

	//visitEnd方法是这个类与StringModifier.java的主要差异,也是核心代码实现的地方
    private static class MethodRegionInjectorMethodVisitor extends MethodVisitor {

        private MethodVisitor chain;
        private String owner;

        private MethodRegionInjectorMethodVisitor(String owner, MethodVisitor chain, MethodNode mn) {
            super(Opcodes.ASM6, mn);
            this.owner = owner;
            this.chain = chain;
        }

        public void visitEnd() {
			...
            if (varIndex > 0) {
            	...
                //由于需要定位return的语句,因此从末尾开始效率更高
				for (int i = instructions.size() - 1; i >= 0; i--) {
                    AbstractInsnNode node = instructions.get(i);
                    int opCode = node.getOpcode();
                    //如果遇到无参的返回指令,则在此之前插入两条指令:LLOAD与INVOKESTATIC
                    if (opCode == Opcodes.RETURN) {
                        VarInsnNode varLoadCall = new VarInsnNode(Opcodes.LLOAD, varIndex);
                        instructions.insertBefore(node, varLoadCall);
                        MethodInsnNode postCall = new MethodInsnNode(Opcodes.INVOKESTATIC,
                                "android/os/Binder","restoreCallingIdentity", "(J)V", false);
                        instructions.insert(varLoadCall, postCall);
                        break;
                    //Add begin
                    //如果是带返回值的返回指令,则需要额外处理
                    } else if (isReturnInstruction(opCode)) {
                        //获取该条返回指令之前的一条指令;
                        AbstractInsnNode previousNode = instructions.get(i - 1);
                        // 如果为为ICONST/BIPUSH/SIPUCH/LDC,则插入xSTORE与xLOAD,再在xLOAD之前插入restoreCallingIdentity(第一种情况)
                        int prevousOpcode = previousNode.getOpcode();
                        if (isConstOpcode(prevousOpcode)) {
                            //需要先创建一个局部变量来保存该值
                            int tmpVarIndex = varIndex + 2;
                            String tmpVarType = getParamTypeByOpcode(prevousOpcode);
                            if (tmpVarType == null) {
                                continue;
                            }
                            LocalVariableNode tmpVarNode = new LocalVariableNode(
                                "asmLocalTmpVar", tmpVarType, null, getFirstLabel(mn), getLastLabel(mn), tmpVarIndex);
                            variables.add(tmpVarNode);
                            mn.maxLocals += 2;
                            mn.maxStack += 2;
                            //插入xSTORE Opcodes.ILOAD
                            VarInsnNode tmpVarStoreCall = new VarInsnNode(getStoreOpcodeByType(tmpVarType), tmpVarIndex);
                            instructions.insertBefore(node, tmpVarStoreCall);
                            //插入xLOAD
                            VarInsnNode tmpVarLoadCall = new VarInsnNode(getLoadOpcodeByType(tmpVarType), tmpVarIndex);
                            instructions.insertBefore(node, tmpVarLoadCall);
                            //在xLOAD之前插入restoreCallingIdentity
                            VarInsnNode varLoadCall = new VarInsnNode(Opcodes.LLOAD, varIndex);
                            instructions.insertBefore(tmpVarLoadCall, varLoadCall);
                            MethodInsnNode postCall = new MethodInsnNode(Opcodes.INVOKESTATIC,
                                    "android/os/Binder","restoreCallingIdentity", "(J)V", false);
                            instructions.insert(varLoadCall, postCall);
                        }
                        // 如果为xLOAD,则在xLOAD之前插入restoreCallingIdentity(第三种情况)
                        else if (isLoadInstruction(prevousOpcode)) {
                            System.out.println("maxLocals = " + mn.maxLocals + ",maxStack = " + mn.maxStack);
                            VarInsnNode varLoadCall = new VarInsnNode(Opcodes.LLOAD, varIndex);
                            instructions.insertBefore(previousNode, varLoadCall);
                            MethodInsnNode postCall = new MethodInsnNode(Opcodes.INVOKESTATIC,
                                    "android/os/Binder","restoreCallingIdentity", "(J)V", false);
                            instructions.insertBefore(previousNode, postCall);
                        }
                        // 如果为其他情况,则在返回指令之前插入restoreCallingIdentity,并在此之后插入xLOAD(第二种情况)
                        else {
                            AbstractInsnNode lastLoadNode = findLastLoadNode(instructions, i - 2);
                            if (lastLoadNode == null) {
                                continue;
                            }

                            VarInsnNode varLoadCall = new VarInsnNode(Opcodes.LLOAD, varIndex);
                            instructions.insertBefore(node, varLoadCall);
                            MethodInsnNode postCall = new MethodInsnNode(Opcodes.INVOKESTATIC,
                                    "android/os/Binder","restoreCallingIdentity", "(J)V", false);
                            instructions.insert(varLoadCall, postCall);

                            AbstractInsnNode clonedLoadNode = lastLoadNode.clone(null);
                            instructions.insert(postCall, clonedLoadNode);
                        }
                    }
                    //Add end
                }
            }
            super.visitEnd();
            mn.accept(chain);
        }

        private LabelNode getFirstLabel(MethodNode mn) {
            for (int i = 0; i < mn.instructions.size(); i++) {
                AbstractInsnNode s = mn.instructions.get(i);
                if (s instanceof LabelNode) {
                    return (LabelNode)s;
                }
            }
            return null;
        }
    
        private LabelNode getLastLabel(MethodNode mn) {
            for (int i = mn.instructions.size() - 1; i >= 0 ; i--) {
                AbstractInsnNode s = mn.instructions.get(i);
                if (s instanceof LabelNode) {
                    return (LabelNode)s;
                }
            }
            return null;
        }

		//Add begin
        private	AbstractInsnNode findNodeByOpcode(MethodNode mn, int opcode) {
            if (opcode <= 0 || mn == null) {
                return null;
            }
            for (int i = mn.instructions.size() - 1; i >= 0 ; i--) {
                AbstractInsnNode s = mn.instructions.get(i);
                if (s.getOpcode() == opcode) {
                    return s;
                }
            }
            return null;
        }

        private boolean isReturnInstruction(int opCode) {
            switch (opCode) {
                case Opcodes.ARETURN:
                case Opcodes.DRETURN:
                case Opcodes.FRETURN:
                case Opcodes.IRETURN:
                case Opcodes.LRETURN:
                    return true;
                default:
                    return false;
            }
        }

        private boolean isConstOpcode(int opCode) {
            switch (opCode) {
                case Opcodes.ICONST_0:
                case Opcodes.ICONST_1:
                case Opcodes.ICONST_2:
                case Opcodes.ICONST_3:
                case Opcodes.ICONST_4:
                case Opcodes.ICONST_5:
                case Opcodes.ICONST_M1:
                case Opcodes.BIPUSH:
                case Opcodes.SIPUSH:
                case Opcodes.LDC:
                    return true;
                default:
                    //TODO
                    return false;
            }
        }

        private String getParamTypeByOpcode(int opCode) {
            switch (opCode) {
                case Opcodes.ICONST_0:
                case Opcodes.ICONST_1:
                case Opcodes.ICONST_2:
                case Opcodes.ICONST_3:
                case Opcodes.ICONST_4:
                case Opcodes.ICONST_5:
                case Opcodes.ICONST_M1:
                case Opcodes.BIPUSH:
                case Opcodes.SIPUSH:
                case Opcodes.LDC:
                    return "I";
                default:
                    //TODO
                    return null;
            }
        }

        private int getLoadOpcodeByType(String type) {
            switch (type) {
                case "I":
                    return Opcodes.ILOAD;
                default:
                    //TODO
                    return -1;
            }
        }

        private int getStoreOpcodeByType(String type) {
            switch (type) {
                case "I":
                    return Opcodes.ISTORE;
                default:
                    //TODO
                    return -1;
            }
        }

        private AbstractInsnNode findLastLoadNode(InsnList instructions, int index) {
            for (;index >= 0; index--) {
                AbstractInsnNode node = instructions.get(index);
                System.out.println("findLastLoadNode-->" + node);
                if (node == null) {
                    continue;
                }
                if (isLoadInstruction(node.getOpcode())) {
                    return node;
                }
            }
            return null;
        }

        private boolean isLoadInstruction(int opCode) {
            switch (opCode) {
                case Opcodes.ALOAD:
                case Opcodes.DLOAD:
                case Opcodes.FLOAD:
                case Opcodes.ILOAD:
                case Opcodes.LLOAD:
                    return true;
                default:
                    return false;
            }
        }
    
        private int getLoadOpCodeByReturnOpcode(int returnOpcode) {
            switch (returnOpcode) {
                case Opcodes.ARETURN:
                return Opcodes.ALOAD;
                case Opcodes.DRETURN:
                return Opcodes.DLOAD;
                case Opcodes.FRETURN:
                return Opcodes.FLOAD;
                case Opcodes.IRETURN:
                return Opcodes.ILOAD;
                case Opcodes.LRETURN:
                return Opcodes.LLOAD;
            }
            return -1;
        }

        private int getConstOpCodeByReturnOpcode(int returnOpcode) {
            switch (returnOpcode) {
                case Opcodes.ARETURN:
                return Opcodes.ALOAD;
                case Opcodes.DRETURN:
                return Opcodes.DLOAD;
                case Opcodes.FRETURN:
                return Opcodes.FLOAD;
                case Opcodes.IRETURN:
                return Opcodes.ILOAD;
                case Opcodes.LRETURN:
                return Opcodes.LLOAD;
            }
            return -1;
        }
        //Add end
    }
}

验证

编译工具端:

ryan ~/workspace/tools $ javac MethodRegionInjector.java -cp ../asm/asm-6.0.jar:../asm/asm-tree-6.0.jar

运行:

ryan ~/workspace/tools $ java -cp ../asm/asm-6.0.jar:../asm/asm-tree-6.0.jar:./ MethodRegionInjector

运行修改后的代码:

ryan ~/workspace/tools $ cd ../out/
ryan ~/workspace/out $ java Test2
clearCallingIdentity-->-5716791008867365700
call-->10731590751
restoreCallingIdentity-->-5716791008867365700
clearCallingIdentity-->2147832164099559499
restoreCallingIdentity-->2147832164099559499
getInt:-1
clearCallingIdentity-->93009951661183094
restoreCallingIdentity-->93009951661183094
getInt:-1352438556
clearCallingIdentity-->1599954344744641456
restoreCallingIdentity-->1599954344744641456
getInt2:1786603818

后记

至此,我们完成了对返回类型为int的方法进行代码注入;
理论上其他基本数据类型是可以类推的,这里暂不展开了;

但是还有如下几个问题没有考虑:

  1. 方法内有多个return;
  2. 方法具备返回值,返回值为引用数据类型;
  3. 方法内有try-catch-finally代码结构;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值