几款Android反编译器对循环结构的还原能力测试记录
本帖最后由 gjden 于 2019-6-26 14:35 编辑几款Android反编译器对循环结构的还原能力测试记录
0、motivation
喜欢jadx的人会常常吐槽JEB反编译器:卖的这么贵,反编译效果还不怎么样。这里我想说的是,JEB毕竟是纯dalvik反编译器,从字节码解析到高级代码生成的整个过程都得从头来过,反编译差点也可以理解( 对于写一款全新的反编译的本人来说深有感触,经典算法和理论也常常有不奏效的时候,因此往往需要改进、优化和扩展,甚至需要提出极端情况下的解决方案,尤其是对抗结构化混淆时)。应该说 JEB 主要槽点是昂贵的费用,其采用的结构化分析技术的确属于比较落后的,但是JEB的功能比jadx更为丰富,更有利于做逆向分析。
我们知道,反编译器是编译器的逆过程,许多反编译器的理论和技术来源于编译理论,这里不细说。反编译器主要包含二进制程序解析、指令(字节码/机器码)解码(句法分析、语义分析)、中间代码生成、控制流图生成、数据流分析、控制流分析、高级代码生成等过程,其中各个阶段都有相关算法来处理。在反编译理论中,控制流分析中最核心的算法是结构化分析算法,结构化算法的优劣决定了反编译器的代码还原能力。而还原循环结构的结构化算法成为反编译实现的一个难点,即便目前的论文已经实现完全无goto的算法,但是这种算法面对复杂混淆后控制结构时还是会出现代码丢失甚至是拒绝工作的情况。
因此,本文简单设计了几种循环类的控制结构来针对性的测试这几款反编译器的还原能力。同时也是为了检验GDA对循环结构的还原能力,发现不足并加以优化。对于反编译结果,我们遵循“语义不变性>代码可读性>代码还原度”的原则。为什么是这个原则,因为语义不变性保证了反编译的代码不会出现程序逻辑上的问题,也就是说,保留程序的等价性,任何输入都能得到同样的输出,出现语义错误在反编译技术领域被视为 不可接受的; 代码可读性需要建立在语义不变性的基础上,更易于人工的阅读和分析; 而代码还原度便是反编译代码与源代码相似程度。
1、Test1
第一个测试中,我设计了循环头和锁节点都为二路条件循环结构,为了测试循环结构化分析能力,我多嵌套了几个if语句(代码标号为基本块号)。程序简单如下:
int a = Math.getExponent(88);
int y = 0;
1 while(y>0){
2 if(a<=0){
3 a=a+1;
y=y+1;
}else{
4 if(a>10){
5 if(a>100){
6 a=a*5;
break;
}else{
7 y=y/a;
}
}
}
}
8 this.attachBaseContext(this);
后面两个图是我做的一张粗略的控制流图,通过android sdk将这段代码生成apk文件后,用Jeb、GDA、Jadx来反编译,并进行代码可读性和语义准确性上进行对比。如下图:
通过对比可以看出,Jeb的还原能力是最差的,其代码可读性比较差,且发生了语义错误,甚至在面对此种循环结构时,还出现了块儿的丢失且多了3个continue。此外还可以看出,JEB反编译对于label的处理是在高级代码输出之后处理的,在做goto-label分析时将其去除,所以导致了空行的存在。GDA看起来是最接近源代码的,且保持了语义的准确性,并且识别出了符号。Jadx代码可读性更好,同时反编译后语义和源代码保持了一致,并且对级联的if-else语句做了优化,但已不再是源代码的样子。
反编译器\评价语义不变性代码可读性代码还原度
GDA√高高
JEB×一般低
Jadx√高高
2、Test2
该测试案例在Test1的基础上仅仅多一条语句,其结果是在循环内的第一个if-else结构之后加了一个后随节点,源代码如下:
int a = Math.getExponent(88);
int y = 0;
1 while(y>0){
2 if(a<=0){
3 a=a+1;
y=y+1;
}else{
4 if(a>10){
5 if(a>100){
6 a = a*5;
break;
}else{
7 y = y/a;
}
}
}
8 y = y*y;
}
9 this.attachBaseContext(this);
编译成为apk后,我们使用JEB、GDA、Jadx来反编译看看效果。
同样GDA几乎做了完美的复原;JEB将其识别为了for类型循环,同样丢失了退出循环的基本块儿(语句a=a*5),导致语义发生错误。Jadx同样也高度的还原了代码,且保持语义的正确性。
反编译器\评价语义不变性代码可读性代码还原度
GDA√高高
JEB×一般低
Jadx√高高
3、Test3
接下来我们来看看他们对双层循环的结构化分析的能力。我设计一个双层循环,使内层循环break出外层循环,实际上基本块5不仅会是内存循环的锁节点,也会是外层循环的锁节点。并且该锁节点为二路条件节点,其一个分支路径回到内层循环,另外一个分支结构回到外层循环。一般对循环结构算法都是循环头-锁节点一一对应,因此处理过程中可能会复杂化该类结构。代码实现非常简单如下:
int a=Math.getExponent(88);
int y=0;
1 while(y>0){
2 while(a>0){
3 if(a<=0){
4 a=a+1;
y=y+1;
}else{
5 if(a>10){
6 break;
}
}
}
}
7 this.attachBaseContext(this);
编译成apk后,再反编译后可以看出GDA反编译的代码,外层循环并没识别出来,但是保持了语义的正确性;
JEB虽然识别出来双层循环,内存循环识别成了do-while结构,另外我们还可以看出其糟糕的goto跳转严重的影响了还原代码的可读性,并且发送语义错误。Jadx基本上还原了原始代码,并保持了语义的正确性。
反编译器\评价语义不变性代码可读性代码还原度
GDA√低低
JEB×低低
Jadx√高高
4、Test4
这一段代码我在退出循环的”if(a>10)”语句中内嵌了另外一个if语句,这会导致内层循环的锁节点发生变化,并且给内层循环添加了一个跟随节点,另外代码做了稍稍的改动。当然代码也非常简单,如下图:
int a=Math.getExponent(88);
int y=0;
1 while(y>0){
2 while(a>50){
3 a=a+1;
y=y+1;
4 if(a>10){
5 if(a>100){
6 a=a*5;
break;
}else{
7 y=y/a;
}
}
}
8 this.attachBaseContext(this);
}
同样我们将该段代码编译成apk后,再反编译:
从反编译结果可以看出GDA保持了语义的不变性,并且几乎完全复原了源代码的结构。JEB虽然代码还原的语义没有发生错误,但是代码还原的质量并不是很好,与源代码相差较大。Jadx还原的代码和源代码几乎完全一样。
反编译器\评价语义不变性代码可读性代码还原度
GDA√高高
JEB√中低
Jadx√高中
5、Test5
我继续在上一个例子的基础上增加一些代码来看看反编译的效果。
int a=Math.getExponent(88);
int y=0;
1 while(y>0){
2 y++;
3 while(a>50){
4 a=a+1;
y=y+1;
5 if(a>10){
6 if(a>100){
7 a=a*5;
continue;
}else{
8 y=y/a;
}
}
9 y=a*y;
break;
}
10 this.attachBaseContext(this);
}
这次我在内层循环的第一个if-else结构上添加一个后随节点,并且最后break出内层循环到外层循环。并且将a=a*5语句后的break改成continue。同样编译成apk后再反编译。
从反编译结果上来看,GDA还原代码时,虽然保持了与源码较高相似性,但是却因丢失了a=a*5语句后的continue语句导致语义错误。而JEB还原代码时,虽然保持语义的正确性,但是代码还原度比较低。Jdax和GDA一样都出现了语义错误,并且不仅丢失了continue语句而且还丢失了break语句。
反编译器\评价语义不变性代码可读性代码还原度
GDA×高中
JEB√中低
Jadx×高中
6、Conclusion
从三款反编译器对循环结构的简单测试中可以看出,Jadx反编译效果最好,利用asm生成class文件后再利用改进的java反编译技术进行反编译,体现了其优势;而对于直接对dalvik字节码进行反编译,另外实现一套反编译引擎的GDA和JEB都会差一点,但是从对循环结构及2路分支结构的恢复上,GDA明显强于JEB,并且对源代码的还原程度也非常高。JEB在测试案例中表现得比较糟糕,甚至在简单情况下就出现了语义错误,这是反编译中无法容忍的,当然本次测试的情况属于极端一点的测试,一般情况下,JEB的循环识别也非常不错。 龙飞雪 发表于 2019-6-26 13:51
想多了,jeb的反编译能力差,呵呵~ jeb的反编译是为了能方便比较复杂的函数,一些复杂的函数其他的反编译器 ...
看来你并不懂什么叫反编译,也没有仔细看文章,复杂函数的处理只是反编译器兼容性问题,和他的结构化算法没有一点关系。 Andy0214 发表于 2019-6-27 08:44
对,楼主只是看到的编译的结果,没有看到一个工具类软件全方面的强大,没有动态调式吧,最初我用GDA基本就 ...
不是只是看到,几款工具根本就没法做全面的对比,那得很多工作量,功能都很丰富。本文的目的在题目和前言已经说得很清楚。此外,对于你这样只懂得伸手用工具的人来说,更深的东西也没法和你讨论。最后,动态调试不是不能做,而是没有时间,写调试器比起写反编译器那都不是一个等级的,此外,Jeb那么大个团队,有钱有人有时间的,你是不是对别人要求太高,GDA只是业余时间的作品。 @gjden 图片盗链了,麻烦上传论坛吧。 Hmily 发表于 2019-6-26 13:15
@gjden 图片盗链了,麻烦上传论坛吧。
ok,正在编辑着:victory: 想多了,jeb的反编译能力差,呵呵~ jeb的反编译是为了能方便比较复杂的函数,一些复杂的函数其他的反编译器都歇菜,只有Jeb能反编译出来,只能说明你逆向还原的代码不多,不够深入~~ 膜拜,如果GDA能加一下类似jeb的可编写插件岂不完美 Anonymous、 发表于 2019-6-26 14:14
膜拜,如果GDA能加一下类似jeb的可编写插件岂不完美
GDA可以支持python脚本,如果支持插件,就必须使用dll形式的插件,这块目前还没有考虑做,的确是没有那么多时间了。至于python接口的问题,如果需要提供什么样的功能,可以提出来,有时间我可以把相关的接口开放出来。 主要反混淆和加密字符串恢复 日常打卡签到 这个是比较全的么,还有没有更全的,有谁测试,哪个最好用啊