#yyds干货盘点#Java ASM系列((093)反编译-方法参数)

学向勤中得,萤窗万卷书。这篇文章主要讲述#yyds干货盘点#Java ASM系列:(093)反编译-方法参数相关的知识,希望能为你提供帮助。
本文属于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: istore4 13: iload_3 14: iload4 16: invokestatic#4// Method java/lang/Math.multiplyExact:(II)I 19: istore5 21: getstatic#5// Field java/lang/System.out:Ljava/io/PrintStream; 24: iload5 26: invokevirtual #6// Method java/io/PrintStream.println:(I)V 29: return LocalVariableTable: StartLengthSlotNameSignature 0300thisLsample/HelloWorld; 0301aI 0302bI 6243sumI 13174diffI 2195 resultI }

【#yyds干货盘点#Java ASM系列((093)反编译-方法参数)】那么,我们能不能利用Java ASM帮助我们做一些反编译的工作呢?
1.2. 整体思路
我们的整体思路就是,结合SourceInterpreter类和LocalVariableTable来对invoke(方法调用)相关的指令进行反编译。
使用SourceInterpreter类输出Frame变化信息:
test:(II)V 000: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_1 1: iload_2 2: 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< SourceValue> analyzer = new Analyzer< > (new SourceInterpreter()); Frame< SourceValue> [] frames = analyzer.analyze(className, mn); // 第二步,获取LocalVariableTable信息 List< LocalVariableNode> 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< SourceValue> f = frames[methodInsn]; int stackSize = f.getStackSize(); List< String> argList = new ArrayList< > (); for (int i = 0; i < argNum; i++) { int stackIndex = stackSize - argNum + i; SourceValue stackValue = https://www.songbingjia.com/android/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< LocalVariableNode> 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< MethodNode> 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来实现的。
  • 第二点,代码示例。如何编码实现对于方法的参数进行反编译。

    推荐阅读