简介
JEB Decompiler对关键字符串进行了混淆,直接反编译得到的源码如下:
public static final String getBuildTypeString() {
StringBuilder var0 = new StringBuilder();
if (isReleaseBuild()) {
var0.append(LQ.QS(new byte[]{-125, 23, 9, 9, 4, 18, 22, 74}, 1, 241));
} else {
var0.append(LQ.QS(new byte[]{-118, 1, 7, 23, 18, 72}, 1, 238));
}
if (isFullBuild()) {
var0.append(LQ.QS(new byte[]{46, 19, 25, 0, 67}, 1, 72));
} else {
var0.append(LQ.QS(new byte[]{39, 10, 29, 22, 93}, 2, 86));
}
...
}
本文介绍了使用Java Deobfuscator对JEB Decompiler进行自动化字符串反混淆的流程。
Java Deobfuscator 是基于ASM的用于Java反混淆的开源项目,采用模块化架构。
ASM
ASM库提供两类API用于生成和转换字节码:基于事件的核心API和基于对象的树API。这两类API可以类比于对XML进行读取的两种API: SAX和DOM。
基于事件API的核心类是ClassVisitor。当一个类的方法、注解、成员被遍历时,ClassVisitor类的对应方法将被调用。
例子:ClassReader类可以读取.class文件,但是怎样才能得到类对应的内容呢?我们可以通过以下代码将字节码转变为基于对象的表示(类似于DOM):
ClassReader cr = new ClassReader(byteCodes);
ClassNode cn = new ClassNode();
cr.accept(cn, 0);
ClassNode继承于ClassVisitor。当调用cr.accept(cn, 0)时,ClassReader遍历类时,将调用ClassVisitor.visit()方法,即ClassNode.visit(),而ClassNode将会相应的信息保存到自身的成员中:
public void ClassNode.visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.version = version;
this.access = access;
this.name = name;
this.signature = signature;
this.superName = superName;
if (interfaces != null) {
this.interfaces.addAll(Arrays.asList(interfaces));
}
。。。
}
注意到ClassWriter也继承于ClassVisitor,那么我们可以这样将读取到的类重新编译为字节码:
ClassReader cr = new ClassReader(byteCodes);
ClassWriter cw = new ClassWriter();
cr.accept(cw, 0);
byte[] byteCode = cw.toByteArray();
或者,增加一些中介ClassVisitor,如ClassNode:
ClassReader cr = new ClassReader(byteCodes);
ClassNode cn = new ClassNode();
cr.accept(cn, 0);
// 对cn进行修改
ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
classfileBuffer = cw.toByteArray();
我们可以在调用cn.accept(cw)前,对cn进行修改,之后传递给ClassWriter保存。
Java 字节码简介
这里简要介绍反混淆时用到的知识。
字节码
Java源代码编译后生成.class文件。该文件包含Java运行所需字节码、元数据等。Java字节码是基于栈的指令集。代码运行时分为两个栈(Stack),局部变量栈(Local Variables Stack)保存函数运行时的临时变量,可以使用Index随机访问;操作数栈(Operand Stack)保存指令运行时需要的参数,先进先出,类似于C++的stack数据结构。
ASM将字节码指令映射到多个指令类,所有指令类均继承于AbstractInsnNode:
LdcInsnNode:读取常量到操作数栈顶。
MethodInsnNode:调用指定函数,函数的所有参数(包含this指针)均从操作数栈上弹出,并压入返回值到栈顶。
VarInsnNode: 从局部变量