最新消息:20210816 当前crifan.com域名已被污染,为防止失联,请关注(页面右下角的)公众号

【整理】java反编译器对比:JD-GUI,CFR,Procyon,Jadx

Java crifan 2768浏览 0评论
折腾:
【整理】把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函数:
  static
  {
    lock = new ReentrantLock();
  }
而CFR、Procyon、Jadx转换结果可以更完整:
CFR
    static {
        userTokens = new HashMap();
        lock = new ReentrantLock();
        condition = lock.newCondition();
    }
Procyon
    static {
        ModelSecurity.userTokens = new HashMap<String, String>();
        lock = new ReentrantLock();
        condition = ModelSecurity.lock.newCondition();
    }
Jadx
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某函数转换报错得不到源码
都是错误代码:
  /* 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
com.huili.readingclub.model.ModelSecurity
----------------------------
getToken(java.lang.String )
  Loose catch block
run()
  Loose catch block
代码中有报错:
    /*
     * 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某函数完美转换无错误:
// 
// 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某函数完美转换外,还保持代码变量和结构更合理-》能识别常量定义和引用
    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很清楚,以及赋值语句:
cArr2[i] = cArr[(b >> 4) & 15];
也容易看懂
    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)的代码:
    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的:
    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导出的源码:
  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数值,而且还有多余的赋值,影响代码逻辑的理解,以及对应的数据赋值:
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源代码

转载请注明:在路上 » 【整理】java反编译器对比:JD-GUI,CFR,Procyon,Jadx

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
82 queries in 0.178 seconds, using 22.12MB memory