开发过程中,我们已经会遇到一些三方库没有开源的情况,有时需要参考其中的一些代码,我们就需要对其进行反编译才能查看。有时还会遇到代码已混淆的情况。本文主要介绍如何使用Threadtear对已混淆的jar包进行反混淆。
Threadtear Git地址:https://github.com/GraxCode/threadtear
编译可执行jar
有两种方式:
1、直接下载Github已经发布的最新版本。(目前的最新版本3.0.1我下载后发现不可正常运行有异常)
2、下载Git项目代码,用Android Studio导入项目。并执行如下两条命令,然后在gui模块的builds/libs中会得到一个可运行的jar文件:threadtear-gui-3.0.0-all.jar
。
gradle build
gradle fatJar
工具使用
在刚才生成的可执行jar文件的目录下(也可以将刚才生成的jar文件拷贝到一个新目录下)运行如下命令,打开Threadtear工具:
java -noverify -jar threadtear-gui-3.0.0-all.jar
工具界面如下图:
help菜单下面有个Look and feel settings可以设置下界面样式,主要是设置下文字大小,默认的大小有点小。。。
接下来我们先设置下要执行的任务,点击Add按钮,如下图:
接下来,我们按顺序将下图所示的任务添加至可执行列表:
再接着,我们将需要反编译反混淆的jar直接拖至右侧Class list区域即可。
这里我还没有找到完全将每个文件的混淆变量全部恢复至原名称的执行列表组合,上门的执行任务只是将每个类名解析了出来。变量名还在研究中,有进展及时更新啦,sorry~
自定义Execution
如上图所示,下载项目源码之后,在core模块中新建custom目录,并创建自定义Execution,并把自定义Execution添加到ExecutionLink中。然后重新打包。这样在可执行jar的ExecutionList中就可以看到我们刚才新建的自定义Execution了。
public class MyExecution extends Execution implements IConstantReferenceHandler {
public MyExecution() {
super(ExecutionCategory.GENERIC, "My execution", "Performs stack analysis and replaces code.");
}
@Override
public boolean execute(Map<String, Clazz> classes, boolean verbose) {
classes.values().stream().map(c -> c.node).forEach(this::analyzeAndRewrite);
return true;
}
public void analyzeAndRewrite(ClassNode cn) {
cn.methods.forEach(m -> {
// this analyzer keeps known stack values, e.g. can be useful for jump prediction
Analyzer<ConstantValue> a = new Analyzer<ConstantValue>(new ConstantTracker(this,
Access.isStatic(m.access), m.maxLocals, m.desc, new Object[0]));
try {
a.analyze(cn.name, m);
} catch (AnalyzerException e) {
logger.error("Failed stack analysis in " + cn.name + "." + m.name + ":" + e.getMessage());
return;
}
Frame<ConstantValue>[] frames = a.getFrames();
InsnList rewrittenCode = new InsnList();
Map<LabelNode, LabelNode> labels = Instructions.cloneLabels(m.instructions);
// rewrite method instructions
for (int i = 0; i < m.instructions.size(); i++) {
AbstractInsnNode ain = m.instructions.get(i);
Frame<ConstantValue> frame = frames[i];
// replace / modify instructions, etc...
if (frame.getStackSize() > 0) {
ConstantValue top = frame.getStack(frame.getStackSize() - 1);
if (top.isKnown() && top.isInteger()) {
int knownTopStackValue = top.getAsInteger();
// use the known stack to remove jumps, simplify code, etc...
// if(...) { rewrittenCode.add(...); }
continue;
}
}
rewrittenCode.add(ain.clone(labels));
}
// update instructions and fix try catch blocks, local variables, etc...
Instructions.updateInstructions(m, labels, rewrittenCode);
});
}
/**
* Use this method to predict stack values if fields are loaded
*/
@Override
public Object getFieldValueOrNull(BasicValue v, String owner, String name, String desc) {
return null;
}
/**
* Use this method to predict stack values if methods are invoked on known objects
*/
@Override
public Object getMethodReturnOrNull(BasicValue v, String owner, String name, String desc, List<?
extends ConstantValue> values) {
if (name.equals("toCharArray") && owner.equals("java/lang/String")) {
if (!values.get(0).isKnown()) {
// invocation target is not known, we can't compute the return
return null;
}
return ((String) values.get(0).getValue()).toCharArray();
}
return null;
}
}