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

1. 如何反编译方法参数

1.1. 提出问题

我们在学习Java的过程中,多多少少都会用到Java Decompiler工具,它可以将具体的.class文件转换成相应的Java代码。

假如有一个HelloWorld类:

public class HelloWorld {    public void test(int a, int b) {        int sum = Math.addExact(a, b);        int diff = Math.subtractExact(a, b);        int result = Math.multiplyExact(sum, diff);        System.out.println(result);    }}

上面的HelloWorld.java经过编译之后会生成HelloWorld.class文件,然后可以查看其包含的instruction内容:

$ javap -v sample.HelloWorld  Compiled from "HelloWorld.java"public class sample.HelloWorld{...  public void test(int, int);    descriptor: (II)V    flags: ACC_PUBLIC    Code:      stack=2, locals=6, args_size=3         0: iload_1         1: iload_2         2: invokestatic  #2                  // Method java/lang/Math.addExact:(II)I         5: istore_3         6: iload_1         7: iload_2         8: invokestatic  #3                  // Method java/lang/Math.subtractExact:(II)I        11: istore        4        13: iload_3        14: iload         4        16: invokestatic  #4                  // Method java/lang/Math.multiplyExact:(II)I        19: istore        5        21: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;        24: iload         5        26: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V        29: return      LocalVariableTable:        Start  Length  Slot  Name   Signature            0      30     0  this   Lsample/HelloWorld;            0      30     1     a   I            0      30     2     b   I            6      24     3   sum   I           13      17     4  diff   I           21       9     5 result   I}

那么,我们能不能利用Java ASM帮助我们做一些反编译的工作呢?

1.2. 整体思路

我们的整体思路就是,结合SourceInterpreter类和LocalVariableTable来对invoke(方法调用)相关的指令进行反编译。

使用SourceInterpreter类输出Frame变化信息:

test:(II)V000:                                 iload_1    {[], [], [], [], [], []} | {}001:                                 iload_2    {[], [], [], [], [], []} | {[iload_1]}002:              invokestatic Math.addExact    {[], [], [], [], [], []} | {[iload_1], [iload_2]}003:                                istore_3    {[], [], [], [], [], []} | {[invokestatic Math.addExact]}004:                                 iload_1    {[], [], [], [istore_3], [], []} | {}005:                                 iload_2    {[], [], [], [istore_3], [], []} | {[iload_1]}006:         invokestatic Math.subtractExact    {[], [], [], [istore_3], [], []} | {[iload_1], [iload_2]}007:                                istore 4    {[], [], [], [istore_3], [], []} | {[invokestatic Math.subtractExact]}008:                                 iload_3    {[], [], [], [istore_3], [istore 4], []} | {}009:                                 iload 4    {[], [], [], [istore_3], [istore 4], []} | {[iload_3]}010:         invokestatic Math.multiplyExact    {[], [], [], [istore_3], [istore 4], []} | {[iload_3], [iload 4]}011:                                istore 5    {[], [], [], [istore_3], [istore 4], []} | {[invokestatic Math.multiplyExact]}012:                    getstatic System.out    {[], [], [], [istore_3], [istore 4], [istore 5]} | {}013:                                 iload 5    {[], [], [], [istore_3], [istore 4], [istore 5]} | {[getstatic System.out]}014:       invokevirtual PrintStream.println    {[], [], [], [istore_3], [istore 4], [istore 5]} | {[getstatic System.out], [iload 5]}015:                                  return    {[], [], [], [istore_3], [istore 4], [istore 5]} | {}================================================================

2. 示例:方法参数反编译

2.1. 预期目标

我们想对HelloWorld.class中的test方法内的invoke相关的instruction进行反编译。

public class HelloWorld {    public void test(int a, int b) {        int sum = Math.addExact(a, b);        int diff = Math.subtractExact(a, b);        int result = Math.multiplyExact(sum, diff);        System.out.println(result);    }}

预期目标:将方法调用的参数进行反编译。

例如,将下面的instructions反编译成Math.addExact(a, b)

0: iload_11: iload_22: invokestatic  #2                  // Method java/lang/Math.addExact:(II)I

2.2. 编码实现

import org.objectweb.asm.Type;import org.objectweb.asm.tree.*;import org.objectweb.asm.tree.analysis.*;import java.util.ArrayList;import java.util.List;public class ReverseEngineerMethodArgumentsDiagnosis {    private static final String UNKNOWN_VARIABLE_NAME = "unknown";    public static void diagnose(String className, MethodNode mn) throws AnalyzerException {        // 第一步,获取Frame信息        Analyzer analyzer = new Analyzer<>(new SourceInterpreter());        Frame[] frames = analyzer.analyze(className, mn);        // 第二步,获取LocalVariableTable信息        List localVariables = mn.localVariables;        if (localVariables == null || localVariables.size() < 1) {            System.out.println("LocalVariableTable is Empty");            return;        }        // 第三步,获取instructions,并找到与invoke相关的指令        InsnList instructions = mn.instructions;        int[] methodInsnArray = findMethodInvokes(instructions);        // 第四步,对invoke相关的指令进行反编译        for (int methodInsn : methodInsnArray) {            // (1) 获取方法的参数            MethodInsnNode methodInsnNode = (MethodInsnNode) instructions.get(methodInsn);            Type methodType = Type.getMethodType(methodInsnNode.desc);            Type[] argumentTypes = methodType.getArgumentTypes();            int argNum = argumentTypes.length;            // (2) 从Frame当中获取指令,并将指令转换LocalVariableTable当中的变量名            Frame f = frames[methodInsn];            int stackSize = f.getStackSize();            List argList = new ArrayList<>();            for (int i = 0; i < argNum; i++) {                int stackIndex = stackSize - argNum + i;                SourceValue stackValue = f.getStack(stackIndex);                AbstractInsnNode insn = stackValue.insns.iterator().next();                String argName = getMethodVariableName(insn, localVariables);                argList.add(argName);            }            // (3) 将反编译的结果打印出来            String line = String.format("%s.%s(%s)", methodInsnNode.owner, methodInsnNode.name, argList);            System.out.println(line);        }    }    public static String getMethodVariableName(AbstractInsnNode insn, List localVariables) {        if (insn instanceof VarInsnNode) {            VarInsnNode varInsnNode = (VarInsnNode) insn;            int localIndex = varInsnNode.var;            for (LocalVariableNode node : localVariables) {                if (node.index == localIndex) {                    return node.name;                }            }            return String.format("locals[%d]", localIndex);        }        return UNKNOWN_VARIABLE_NAME;    }    public static int[] findMethodInvokes(InsnList instructions) {        int size = instructions.size();        boolean[] methodArray = new boolean[size];        for (int i = 0; i < size; i++) {            AbstractInsnNode node = instructions.get(i);            if (node instanceof MethodInsnNode) {                methodArray[i] = true;            }        }        int count = 0;        for (boolean flag : methodArray) {            if (flag) {                count++;            }        }        int[] array = new int[count];        int j = 0;        for (int i = 0; i < size; i++) {            boolean flag = methodArray[i];            if (flag) {                array[j] = i;                j++;            }        }        return array;    }}

2.3. 进行分析

HelloWorldAnalysisTree类当中,要注意:不能使用ClassReader.SKIP_DEBUG,因为我们要使用到MethodNode.localVariables字段的信息。

public class HelloWorldAnalysisTree {    public static void main(String[] args) throws Exception {        String relative_path = "sample/HelloWorld.class";        String filepath = FileUtils.getFilePath(relative_path);        byte[] bytes = FileUtils.readBytes(filepath);        //(1)构建ClassReader        ClassReader cr = new ClassReader(bytes);        //(2)生成ClassNode        int api = Opcodes.ASM9;        ClassNode cn = new ClassNode(api);        int parsingOptions = ClassReader.SKIP_FRAMES;        cr.accept(cn, parsingOptions);        //(3)进行分析        String className = cn.name;        List methods = cn.methods;        MethodNode mn = methods.get(1);        ReverseEngineerMethodArgumentsDiagnosis.diagnose(className, mn);    }}

输出结果:

java/lang/Math.addExact([a, b])java/lang/Math.subtractExact([a, b])java/lang/Math.multiplyExact([sum, diff])java/io/PrintStream.println([result])

3. 总结

本文内容总结如下:

  • 第一点,整体的思路,是利用SourceInterpreter类和LocalVariableTable来实现的。
  • 第二点,代码示例。如何编码实现对于方法的参数进行反编译。