折腾:
【整理】把jar包转换为java源代码的java反编译器的整理和对比
期间,去对比几款java反编译工具,看看从jar包反编译转换为java代码的效果有何区别。
而关于:
JD-GUI和(基于Procyon的)Luyten两者对比:
jd-gui | Luyten | |
流行程度 | 比较广-》大家用的比较多 | 一般-》相对使用的人不是很多 |
jar转java的准确率 | 高 -》jd-gui代码转换出错Error: ![]() | 很高 -》Luyten中可正确解析显示源码: ![]() |
显示:整体UI界面和代码高亮 | 比较简单和朴素-》不够炫酷和好看 ![]() | 好看,而且支持多种语法高亮效果 -》 更方便看代码 ![]() |
显示:包/类的显示结构 | 以目录的树状结构显示的 -》 层次比较清晰 ![]() | (默认)平铺直叙,直接显示的 -》层次结构不够清晰 ![]() 后记: 后来发现,把默认勾选的选项 Operation-> Package Explorer Style 取消勾选,也可以按照树状显示类和包名了: ![]() |
附录:
【Luyten的语法高亮的不同主题的效果】
默认:Default = Default-Alt

Dark

Eclipse

Visual Studio

IntelliJ

【后记:jar转java效果对比:JD-GUI vs CFR vs Procyon vs Jadx】
详见:
- JD-GUI
- 【已解决】mac版JD-GUI查看并导出jar包的java源代码
- CFR
- 【已解决】用java反编译器CFR从jar包导出java源代码
- Procyon
- 【已解决】用Procyon命令行去从jar包导出java源代码
- Jadx
- 【已解决】用jadx把安卓dex文件转换提取出jar包和java源代码
对于同一个jar包:
com.huili.readingclub.jar

用不同工具:
- JD-GUI
- CFR
- Procyon
- Jadx
去导出java源代码后,转换导出的效果,尤其是准确性,是否出错,还是不太一样的:
比如:
【JD-GUI细节不够好,CFR细节基本满足要求,Procyon细节完美转换,Jadx不仅完美且代码变量和结构更合理】
jd-gui的细节不够好的地方:
static函数:
1 2 3 4 | static { lock = new ReentrantLock(); } |

而CFR、Procyon、Jadx转换结果可以更完整:
CFR
1 2 3 4 5 | static { userTokens = new HashMap(); lock = new ReentrantLock(); condition = lock.newCondition(); } |

Procyon
1 2 3 4 5 | static { ModelSecurity.userTokens = new HashMap<String, String>(); lock = new ReentrantLock(); condition = ModelSecurity.lock.newCondition(); } |

Jadx
1 2 3 4 | public class ModelSecurity { private static final Condition condition = lock.newCondition(); private static final Lock lock = new ReentrantLock(); private static HashMap<String, String> userTokens = new HashMap(); |

很明显从:
- CFR的:userTokens = new HashMap();
- Procyon的:ModelSecurity.userTokens = new HashMap<String, String>();
- Jadx的:private static HashMap<String, String> userTokens = new HashMap();
可以看出:
Procyon能识别static变量,细节转换的更完美更精确。
而Jadx更进一步:
识别出是private的static类型的变量,更加准确。
而:
【JD-GUI某函数转换报错得不到源码,CFR某函数转换报错但有源码,Procyon某函数完美转换无错误,Jadx某函数完美转换外还能识别常量定义和代码结构更清晰】
比如:
com.huili.readingclub.model.ModelSecurity的getToken
JD-GUI某函数转换报错得不到源码
都是错误代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /* Error */ private static String getToken(String paramString) { // Byte code: // 0: aload_0 // 1: invokestatic 81 com/huili/readingclub/utils/StringUtil:isNullOrEmpty (Ljava/lang/String;)Z // 4: ifeq +5 -> 9 // 7: aconst_null // 8: areturn // 9: aload_0 // 10: invokestatic 87 java/lang/Integer:parseInt (Ljava/lang/String;)I // 13: iconst_1 // 14: if_icmpge +5 -> 19 // 17: aconst_null // 18: areturn // 19: getstatic 22 com/huili/readingclub/model/ModelSecurity:userTokens Ljava/util/HashMap; // 22: aload_0 ...... |

CFR某函数转换报错但有源码
转换报错:
summary.txt
1 2 3 4 5 6 | com.huili.readingclub.model.ModelSecurity ---------------------------- getToken(java.lang.String ) Loose catch block run() Loose catch block |
代码中有报错:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | /* * WARNING - Removed back jump from a try to a catch block - possible behaviour change. * Loose catch block * Enabled aggressive block sorting * Enabled unnecessary exception pruning * Enabled aggressive exception aggregation * Lifted jumps to return sites */ private static String getToken(final String string2) { ...... lock.lock(); Runnable runnable = new Runnable((String)charSequence){ ...... /* * WARNING - Removed back jump from a try to a catch block - possible behaviour change. * Loose catch block * Enabled aggressive block sorting * Enabled unnecessary exception pruning * Enabled aggressive exception aggregation * Lifted jumps to return sites */ @Override public void run() { Throwable throwable2222; |

Procyon某函数完美转换无错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // // Decompiled by Procyon v0.5.34 // private static String getToken(String s) { if (StringUtil.isNullOrEmpty(s)) { return null; } ... sb.append(“AyGt7ohMR!xxx #N"); ModelSecurity.lock.lock(); try { try { new Thread(new Runnable() { @Override public void run() { ModelSecurity.lock.lock(); try { try { final StringBuilder sb = new StringBuilder(); ... sb.append( "/" ); |

Jadx某函数完美转换外,还保持代码变量和结构更合理-》能识别常量定义和引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | private static String getToken(final String str ) { if (StringUtil.isNullOrEmpty( str ) || Integer.parseInt( str ) < 1 ) { return null; } ...... stringBuilder.append(MyConfig.SECRET_KEY); final String md5 = StringUtil.md5(stringBuilder.toString()); lock.lock(); try { new Thread(new Runnable() { public void run() { ModelSecurity.lock.lock(); try { StringBuilder stringBuilder = new StringBuilder(); ... stringBuilder.append(HttpUtils.PATHS_SEPARATOR); |

其中包括stringBuilder.append的参数是:
常量定义 MyConfig.SECRET_KEY
而不是之前代码的 常量的值:
“AyGt7ohMR!xxx#N”
另外,再去对比:
Jadx导出的代码的逻辑和结构,非常清晰:
char的list很清楚,以及赋值语句:
1 | cArr2[i] = cArr[(b >> 4) & 15]; |
也容易看懂

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public static final String getMD5Str(String str ) { char[] cArr = new char[]{ '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' }; try { byte[] bytes = str .getBytes(); MessageDigest instance = MessageDigest.getInstance( "MD5" ); instance.update(bytes); char[] cArr2 = new char[(r1 * 2 )]; int i = 0 ; for (byte b : instance.digest()) { int i2 = i + 1 ; cArr2[i] = cArr[(b >> 4 ) & 15 ]; i = i2 + 1 ; cArr2[i2] = cArr[b & 15 ]; } return new String(cArr2); } catch (Exception unused) { return null; } } |
而不是像:
Procyon(的Luyten)的代码:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | public static final String getMD5Str(final String s) { final char[] array2; final char[] array = array2 = new char[ 16 ]; array2[ 0 ] = '0' ; array2[ 1 ] = '1' ; array2[ 2 ] = '2' ; array2[ 3 ] = '3' ; array2[ 4 ] = '4' ; array2[ 5 ] = '5' ; array2[ 6 ] = '6' ; array2[ 7 ] = '7' ; array2[ 8 ] = '8' ; array2[ 9 ] = '9' ; array2[ 10 ] = 'a' ; array2[ 11 ] = 'b' ; array2[ 12 ] = 'c' ; array2[ 13 ] = 'd' ; array2[ 14 ] = 'e' ; array2[ 15 ] = 'f' ; try { final byte[] bytes = s.getBytes(); final MessageDigest instance = MessageDigest.getInstance( "MD5" ); instance.update(bytes); final byte[] digest = instance.digest(); final int length = digest.length; final char[] array3 = new char[length * 2 ]; int i = 0 ; int n = 0 ; while (i < length) { final byte b = digest[i]; final int n2 = n + 1 ; array3[n] = array[b >> 4 & 0xF ]; n = n2 + 1 ; array3[n2] = array[b & 0xF ]; + + i; } return new String(array3); } catch (Exception ex) { return null; } } |
作为一个char的list,还是没有清晰的表达出来
更不像是CFR的:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public static final String getMD5Str(String object ) { char[] arrc; char[] arrc2 = arrc = new char[ 16 ]; arrc2[ 0 ] = 48 ; arrc2[ 1 ] = 49 ; arrc2[ 2 ] = 50 ; arrc2[ 3 ] = 51 ; arrc2[ 4 ] = 52 ; arrc2[ 5 ] = 53 ; arrc2[ 6 ] = 54 ; arrc2[ 7 ] = 55 ; arrc2[ 8 ] = 56 ; arrc2[ 9 ] = 57 ; arrc2[ 10 ] = 97 ; arrc2[ 11 ] = 98 ; arrc2[ 12 ] = 99 ; arrc2[ 13 ] = 100 ; arrc2[ 14 ] = 101 ; arrc2[ 15 ] = 102 ; object = object .getBytes(); char[] arrc3 = MessageDigest.getInstance( "MD5" ); arrc3.update((byte[]) object ); object = arrc3.digest(); int n2 = ((byte[]) object ).length; arrc3 = new char[n2 * 2 ]; int n3 = 0 ; for ( int i2 = 0 ; i2 < n2; + + i2) { byte by = object [i2]; int n4 = n3 + 1 ; arrc3[n3] = arrc[by >> 4 & 15 ]; n3 = n4 + 1 ; arrc3[n4] = arrc[by & 15 ]; } try { object = new String(arrc3); return object ; } catch (Exception exception) { return null; } } |
连char的list都不够明显,只是char的int数值。
更更不像是JD-GUI导出的源码:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | public static final String getMD5Str(String paramString) { char[] arrayOfChar = new char[ 16 ]; char[] tmp6_5 = arrayOfChar; tmp6_5[ 0 ] = 48 ; char[] tmp12_6 = tmp6_5; tmp12_6[ 1 ] = 49 ; char[] tmp18_12 = tmp12_6; tmp18_12[ 2 ] = 50 ; char[] tmp24_18 = tmp18_12; tmp24_18[ 3 ] = 51 ; char[] tmp30_24 = tmp24_18; tmp30_24[ 4 ] = 52 ; char[] tmp36_30 = tmp30_24; tmp36_30[ 5 ] = 53 ; char[] tmp42_36 = tmp36_30; tmp42_36[ 6 ] = 54 ; char[] tmp49_42 = tmp42_36; tmp49_42[ 7 ] = 55 ; char[] tmp56_49 = tmp49_42; tmp56_49[ 8 ] = 56 ; char[] tmp63_56 = tmp56_49; tmp63_56[ 9 ] = 57 ; char[] tmp70_63 = tmp63_56; tmp70_63[ 10 ] = 97 ; char[] tmp77_70 = tmp70_63; tmp77_70[ 11 ] = 98 ; char[] tmp84_77 = tmp77_70; tmp84_77[ 12 ] = 99 ; char[] tmp91_84 = tmp84_77; tmp91_84[ 13 ] = 100 ; char[] tmp98_91 = tmp91_84; tmp98_91[ 14 ] = 101 ; char[] tmp105_98 = tmp98_91; tmp105_98[ 15 ] = 102 ; tmp105_98; try { paramString = paramString.getBytes(); Object localObject = MessageDigest.getInstance( "MD5" ); ((MessageDigest)localObject).update(paramString); paramString = ((MessageDigest)localObject).digest(); int i = paramString.length; localObject = new char[i * 2 ]; int j = 0 ; int k = 0 ; while (j < i) { int m = paramString[j]; int n = k + 1 ; localObject[k] = ((char)arrayOfChar[(m >> 4 & 0xF )]); k = n + 1 ; localObject[n] = ((char)arrayOfChar[(m & 0xF )]); j + + ; } paramString = new String((char[])localObject); return paramString; } catch (Exception paramString) {} return null; } |
连char的list不近不够明显,不近只是char的int数值,而且还有多余的赋值,影响代码逻辑的理解,以及对应的数据赋值:
1 | localObject[k] = ((char)arrayOfChar[(m >> 4 & 0xF)]); |
都很晦涩难懂。
【总结】
java反编译器 | JD-GUI | CFR | Procyon | Jadx |
转换出错程度 | 比较多 | 少许 | 很少 | 极少 |
出错状态和相关信息 | 会显示: /* Error */ // Byte code | 输出转换期间出错的地方到文件:summary.txt | 会显示: This method could not be decompiled. Original Bytecode | |
转换出的代码的质量 | 不是很好,细节不够好 | 部分细节转换的略有瑕疵,但是还是可以看到基本代码逻辑的 | 完美转换细节 代码中字符数组定义等内容可以正确转换,但是逻辑不清晰 比如只是能转换出常量解析后的字符串值,而无法识别出是常量的引用 | 不仅转换完美无错,而且代码逻辑更准确和完整 代码中的常量的引用都能完美还原出来 代码中字符数组定义等内容可以完美转换 |
转换出的文件是否有标识 | 无 | 有,顶部有标识: Decompiled with CFR 0.141 并且还标出哪些转换出错: Could not load the following classes: android.app.Dialog android.content.Context android.content.res.Resources android.view.View android.view.View$OnClickListener android.widget.TextView | 有,顶部有标识: Decompiled by Procyon v0.5.34 | 无 |
【总结】
- 以后尽量用Jadx
- 直接用jadx打开未加固的apk
- 即可查看和导出java源代码
- 打开(用FDex2从加固了的apk导出的)dex文件
- 查看和导出java源代码
- 其次考虑用Procyon(或基于Procyon的GUI工具Luyten)
- 查看代码:用基于Procyon的Luyten去查看代码
- 导出代码:用Procyon去从jar反编译转换出java源代码