• CLion注冊码算法逆向分析实录(纯研究)


    声明

    CLion程序版权为jetBrains全部、注冊码授权为jetBrains及其付费用户全部,本篇仅仅从兴趣出发,研究其注冊码生成算法。

    • 不会释出不论什么完整的源码.
    • 网上查了下。已有注冊机,所以想要key的同学不要找我:p

    背景

    打算学习cocos2dx,奈何vim仅仅会ggvG,被jetBrains惯坏了,找到了CLion,试了下,果然神器。我等菜鸟正好能够拿来愉快地学习书写c++了。

    可是,试用版有30天的限制。又没有学生授权。懒得折腾,看下它的注冊算法吧。

    本篇用到的主要工具和命令:

    • jd-gui 1.2
    • jdb
    • zip/unzip
    • jar

    找出注冊算法的代码段

    先用jd-gui看下反编译的效果,大概找了下,看到这个MainImpl类:
    这里写图片描写叙述

    反编译的效果还是能够,只是注意到一些类和方法的名称被混淆了。如上图的a.a().a(new a.a_())....

    尝试直接建立java文件。发现非常多类缺失,比較难补,遂放弃。

    尝试用java attachagent方式来dump执行时的class,比較难偷,遂放弃。

    换换思路

    CLion启动后,假设未输入正确的注冊码,或者没有选择试用,会打开一个对话框,提示输入注冊码。

    例如以下图:
    这里写图片描写叙述

    假设选择试用,先进入主界面。打开关于对话框。能够看到部分授权信息。

    例如以下图:
    这里写图片描写叙述

    好。从这两个点出发,大概找找相关代码,缩小分析范围:

    分析MainImpl启动前后的类载入信息

    改动$CLION_HOME/bin下的clion.sh
    这里写图片描写叙述

    在启动參数中,添加-verbose.

    又一次启动CLion,收集到MainImpl载入前后的一些信息。例如以下图:

    这里写图片描写叙述

    继续翻看,又发现例如以下一些信息:

    这里写图片描写叙述

    发现 com.intellij.ide.a包和com.intellij.a包严重混淆,而且在MainImpl载入前后出现.
    能够初步判定这些包是分析重点.

    分析AboutPopup类的信息

    打开关于/About对话框,观察比对代码,发现例如以下信息:
    这里写图片描写叙述

    在粗略分析相关代码后,跟踪到这里:

    这里写图片描写叙述

    LicensingFacade是个抽象类,跟一些实现,发现仅仅有一个子类:

    这里写图片描写叙述

    接着跟,又发现一个抽象类bb

    这里写图片描写叙述

    这个类在com.intellij.ide.a.g包下。当中一段关键代码:

    public void a(q.a_ parama_)
      {
        boolean bool = GraphicsEnvironment.isHeadless();
    
        f();
        a(this.f);
        /// 这里的u()是关键
        for (s locals : u())
        {
          if (locals.d() == null) {
            locals.c();
          }
          com.intellij.a.b.e locale = locals.d();
          if (locale != null)
          {
            g_ localg_ = a(parama_, locals);
            if (localg_ != g_.SKIP)
            {
              UpdateChecker.addUpdateRequestParameter("license", a(locale));
              if (localg_ != g_.OK) {
                break;
              }
              int i1 = (!bool) && (a(locale, locale instanceof l)) ?

    1 : 0; if (i1 == 0) { a(parama_, locals, locale); return; } break; } } } if (bool) { b.error("No valid license found"); System.exit(-1); } else { b(parama_); } }

    接着看com.intellij.ide.a.g.bb.u()方法:

    private s[] u()
      {
        s[] arrayOfs = v();
        Object localObject1 = null;
        for (Object localObject3 : arrayOfs)
        {
          try
          {
            ((s)localObject3).c();
            if (((s)localObject3).d() != null) {
    
              /// 这里的b()是关键
              ((s)localObject3).b();
            } else {
              continue;
            }
          }
          catch (Exception localException)
          {
            continue;
          }
          localObject1 = localObject3;
          break;
        }
        if (localObject1 != null)
        {
          ??? = new ArrayList(Arrays.asList(arrayOfs));
          ((ArrayList)??

    ?).remove(localObject1); ((ArrayList)???).add(0, localObject1); return (s[])((ArrayList)??

    ?).toArray(new s[((ArrayList)???

    ).size()]); } return arrayOfs; }

    中间的过程比較繁琐。考验的是耐心、猜測、和笔记功夫。
    通过相关代码的类型分析、方法分析、參数分析、变量分析,逐步缩小关键代码段的分析范围,同一时候添加对混淆代码的熟悉。


    分析注冊码算法解密验证的重点包

    结合上面的对于MainImpl载入前后的类信息分析。重点关注com.intellij.a.g这个包.

    终于跟到这样一个类com.intellij.a.g.c

    package com.intellij.a.g;
    
    import java.math.BigInteger;
    
    public class c
    {
    
    /// 这个a是关键。通过后面的实验,这是RSA加密算法的公钥部分的n .
      private static BigInteger a = new BigInteger("8eeb1420b7d8b90aa3b95c7ff73628e46e12c19dc91531be5f517a54b042e99c17445ce7b23834a3ec80d2691b463231be43aab7e897cc334bc9b8bb9f0d55f5", 16);
      private static BigInteger b = new BigInteger("10001", 16);
    
      /// 这种方法是关键
      /// 通过猜測和实验。能够确定paramString1和paramString2是username和代码,或者相反。
      public static h a(String paramString1, String paramString2)
        throws a
      {
        // 这个h类型是关键,记录了注冊码类型、产品类型、username、过期时间、维护到期时间、主版本、小版本等关键信息
        h localh = new h();
    
        // 这里m.a(,,,)方法完毕第一次解密
        byte[] arrayOfByte = m.a(paramString1, localh, b, a);
        // m.a(,,,)方法验证注冊码解密后的字节信息。从中提取关键属性
        m.a(arrayOfByte, localh, paramString2, 14);
        // 继续验证
        m.b(arrayOfByte, localh, paramString2, -1);
        return localh;
      }
    }

    为了确定静态分析过程的正确性,使用jdb来看一下简单的调用栈:

    Idea Main Thread 1.0.4#CL-141.874.66, eap:false[1] wherei 0x6f4
    
      /// 能够确定。每次载入MainImpl。调用其start方法,都会来以下这种方法中验证注冊码,这是迄今为止最为关键的发现
      [1] com.intellij.a.g.c.a (c.java:18), pc = 13
    
      [2] com.intellij.ide.a.b.b$0.b (b$0.java:71), pc = 2
      [3] com.intellij.ide.a.b.b$0.a (b$0.java:67), pc = 3
      [4] com.intellij.a.b.d.x (d.java:34), pc = 12
      [5] com.intellij.a.b.d.t (d.java:13), pc = 1
      [6] com.intellij.a.b.b.s (b.java:40), pc = 10
      [7] com.intellij.a.b.b.c (b.java:33), pc = 1
      [8] com.intellij.ide.a.g.t.h (t.java:108), pc = 41
      [9] com.intellij.ide.a.g.t.a (t.java:27), pc = 8
      [10] com.intellij.ide.a.g.m.b (m.java:29), pc = 27
    
      // bb类是个非常重要的类。兴许分析
      [11] com.intellij.ide.a.g.bb.u (bb.java:152), pc = 48
      [12] com.intellij.ide.a.g.bb.a (bb.java:78), pc = 18
      [13] com.intellij.idea.MainImpl$1.start (MainImpl.java:45), pc = 19
      [14] com.intellij.idea.StartupUtil.prepareAndStart (StartupUtil.java:117), pc = 115
      [15] com.intellij.idea.MainImpl.start (MainImpl.java:40), pc = 9
      [16] sun.reflect.NativeMethodAccessorImpl.invoke0 (native method)
      [17] sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62), pc = 100
      [18] sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43), pc = 6
      [19] java.lang.reflect.Method.invoke (Method.java:497), pc = 56
      [20] com.intellij.ide.plugins.PluginManager$2.run (PluginManager.java:91), pc = 53
      [21] java.lang.Thread.run (Thread.java:745), pc = 11

    分析注冊码字节数组

    
    /// 我们重点跟一下com.intellij.a.g.c.a()方法:
    
    package com.intellij.a.g;
    
    import java.math.BigInteger;
    
    public class c
    {
      private static BigInteger a = new BigInteger("8eeb1420b7d8b90aa3b95c7ff73628e46e12c19dc91531be5f517a54b042e99c17445ce7b23834a3ec80d2691b463231be43aab7e897cc334bc9b8bb9f0d55f5", 16);
      private static BigInteger b = new BigInteger("10001", 16);
    
      /// 重点跟这种方法
      public static h a(String paramString1, String paramString2)
        throws a
      {
        h localh = new h();
    
        byte[] arrayOfByte = m.a(paramString1, localh, b, a);
        m.a(arrayOfByte, localh, paramString2, 14);
        m.b(arrayOfByte, localh, paramString2, -1);
        return localh;
      }
    }

    在上面的方法中,类型h非常关键。这里的字符串没有混淆,给我们猜測注冊码结构提供了极大的便利 O^. ^O.

        /// 这里的一些字段和变量的意思,已经给出来了
        public String toString() {
            StringBuffer var1 = new StringBuffer();
            var1.append("
    ");
            var1.append("user name:" + this.b + "
    ");
            var1.append("customer id:" + this.c + "
    ");
            var1.append("product id:" + j.a(this.d) + "
    ");
            var1.append("license type:" + i.a(this.e) + "
    ");
            var1.append("major version:" + this.f + "
    ");
            var1.append("minor version:" + this.g + "
    ");
            var1.append("generationDate:" + this.h + "
    ");
            var1.append("expirationDate:" + this.i + "
    ");
            return var1.toString();
        }
    
        static {
            a.b = "Fake";
            a.d = 0;
            a.e = 1;
            a.f = 1;
            a.g = 0;
            a.h = d.a(1995, 1, 1);
            a.i = d.a(1995, 1, 2);
        }

    跟这种方法:byte[] arrayOfByte = m.a(paramString1, localh, b, a);

    protected static byte[] a(String var0, h var1, BigInteger var2, BigInteger var3) throws a {
            Matcher var4 = c.matcher(var0);
            var0 = var4.replaceAll("").trim();
    
            // 10是ASCII码中的"
    "换行符
            // 13是ASCII码中的"
    "回车符
            // 以下的代码告诉我们一个重要信息:
            // 加密的注冊码至少包括1个
    (linux)或者
    (windox|mac)
            int var5 = var0.indexOf(10);
            if(var5 < 0) {
                var5 = var0.indexOf(13);
            }
    
            if(var5 < 0) {
                throw new a();
            } else {
                // 45是ASCII码中的"-"
                // 重要信息:加密的注冊码至少包括一个"-",且出如今"
    "或"
    "之前
                String var6 = var0.substring(0, var5);
                //"-"
                int var7 = var6.indexOf(45);
                if(var7 > 0) {
                    try {
    
                        // 从[xx-?

    ? ?

    ??]中提取xx,并赋值给var1.c // 依据上面的h类型的信息,这里的xx是cutomerId,所以我们有了: // license : customerId-?? ???

    var1.c = Integer.parseInt(var6.substring(0, var7)); } catch (NumberFormatException var9) { throw new a(); } } else { var1.c = -1; } // 这里能够确定传入的var2是公钥的n,var3是公钥的e // 同一时候依据RSADecoder我们也找到了相应的RSAEncoder,兴许直接拷贝RSAEncoder过来,作注冊码加密用^^ return (new RSADecoder(var2, var3, 64)).decode(var0.substring(var5 + 1)); } }

    跟这种方法:m.a(arrayOfByte, localh, paramString2, 14);

    static void a(byte[] var0, h var1, String var2, int var3) throws a {
            //这里,我们依据传入的var3 = 14,得知:
            //license的长度为14个字节
            if(var0.length != var3) {
                byte[] var4;
                if(var0.length == var3 + 1) {
                    if(var0[0] != 0) {
                        throw new a();
                    }
    
                    var4 = new byte[var3];
                    System.arraycopy(var0, 1, var4, 0, var3);
                    var0 = var4;
                } else {
                    if(var0.length >= var3) {
                        throw new a();
                    }
    
                    var4 = new byte[var3];
                    System.arraycopy(var0, 0, var4, var3 - var0.length, var0.length);
                    var0 = var4;
                }
            }
    
        // 这里我们依据分析,得到:
            //var2 : userName , var1.c : customerId , var3 : 14
    
            // 假设lic是个14字节长的字节数组,我们有:
            //lic[12] = customerId & 0xFF;
            //lic[13] = customerId >> 8 & 0xFF;
    
            if(var2 != null) {
            /* 
                以下的o.a(,,)方法的实现:
                能够直接把这段代码copy到我们做注冊码的地方^^
    
            public static short a(String var0, int var1, byte[] var2) {
                CRC32 var3 = new CRC32();
                int var4;
                if(var0 != null) {
                    for(var4 = 0; var4 < var0.length(); ++var4) {
                        char var5 = var0.charAt(var4);
                        var3.update(var5);
                    }
                }
    
                var3.update(var1);
                var3.update(var1 >> 8);
                var3.update(var1 >> 16);
                var3.update(var1 >> 24);
    
                for(var4 = 0; var4 < var2.length - 2; ++var4) {
                    byte var6 = var2[var4];
                    var3.update(var6);
                }
    
                return (short)((int)var3.getValue());
            }
             */
    
                short var5 = o.a(var2, var1.c, var0);
            // 这里得到:
            // lic[12] = crc32 & 0xFF
            // lic[13] = crc32 >> 8 & 0xFF
                if(var0[var3 - 2] != (byte)(var5 & 255)) {
                    throw new a();
                }
    
                if(var0[var3 - 1] != (byte)(var5 >> 8 & 255)) {
                    throw new a();
                }
            }

    努力,继续跟这种方法:m.b(arrayOfByte, localh, paramString2, -1);

     static void b(byte[] var0, h var1, String var2, int var3) throws a {
            try {
           /** 对照
                   var1.append("user name:" + this.b + "
    ");
               var1.append("customer id:" + this.c + "
    ");
               var1.append("product id:" + j.a(this.d) + "
    ");
               var1.append("license type:" + i.a(this.e) + "
    ");
               var1.append("major version:" + this.f + "
    ");
               var1.append("minor version:" + this.g + "
    ");
               var1.append("generationDate:" + this.h + "
    ");
               var1.append("expirationDate:" + this.i + "
    ");
           */
    
           // licTypeId是注冊码类型的Id,prodId是产品类型的Id
               /**
               * lic[0] = licTypeId << 4 + (prodId & 0xFF);
           */
    
                var1.b = var2;
                var1.d = var0[0] & 15;
                var1.e = var0[0] >> 4;
                var1.g = var0[1] >> 4;
    
            //这里得到generationDate信息,也就是now了。以毫秒表示,并带符号右移16位,然后按4字节存储
                /**
                * long now = System.currentTimeMillis() >> 16;
                * lic[2] = now & 0xFF;
                * lic[3] = now >> 8 & 0xFF;
                * lic[4] = now >> 16 & 0xFF;
                * lic[5] = now >> 24 & 0xFF;
                */
    
                long var4 = ((long)var0[2] & 255L) + (((long)var0[3] & 255L) << 8) + (((long)var0[4] & 255L) << 16) + (((long)var0[5] & 255L) << 24) << 16;
                var1.h = new Date(var4);
    
                /*
                * 注意到上面有句 :var1.g = var0[1] >> 4;
                * 结合以下的代码,我们有:
                * lic[1] = minorVer >> 4 + (majorVer & 0xFF);
                */
    
                var1.f = var0[1] & 15;
    
                /**
                * lic[6] = delta & 0xFF;
                * lic[7] = delta >> 8 & 0xFF;
                */
    
                int var6 = (var0[6] & 255) + ((var0[7] & 255) << 8);
                if(var6 != 0) {
                    //expire date
                    var1.i = new Date(var4 + (long)var6 * 24L * 60L * 60L * 1000L);
                }
    
                //var3:-1 
                //lic[10] = any
                //lic[11] = any
                int var7 = var3 > -1?var3:(var0[10] & 255) + ((var0[11] & 255) << 8);
                if(var6 != 0) {
                    var7 = var6;
                }
    
            //var1.l = maintenanceDueDate ,维护到期时间
                var1.l = new Date(var4 + (long)var7 * 24L * 60L * 60L * 1000L);
            } catch (ArrayIndexOutOfBoundsException var8) {
                throw new a();
            }
        }

    得到产品信息和注冊码类型信息

    如今我们基本拿到了注冊码的字节信息,还差一点。产品信息和注冊码类型信息这里处理的是Id,那么实际信息在哪里?

    还记得我们前面说过一个bb类吧。对它的子类进行分析。能够得到非常多相似这种代码:

        static a a(com.intellij.ide.a.r var0, q var1) {
            return new a(var0, "AppCode", 8, Products.APPCODE, 3, 30, "6", var1, new g() {
                public h b(String var1, String var2) throws com.intellij.a.g.a {
                    return com.intellij.a.g.b.a(var1, var2);
                }
            });
        }

    看来jetBrains的非常多产品的加密算法都是差点儿相同,真是比較懒啊.

    通过分析bb类所在的包com.intellij.ide.a.a,以及com.intellij.a.g包,我们得到例如以下实用信息:
    这里写图片描写叙述
    以及:
    这里写图片描写叙述


    写出注冊码生成算法

    • 关键字节数组。14字节长:

    这里写图片描写叙述

    • CRC校验:

    package rsa;
    
    import java.util.zip.CRC32;
    
    public class GroupUtil {
        public static final int a = 12;
        public static final int b = 14;
        public GroupUtil() {
        }
    
        public static short computeCRC32(String userName, int customerId, byte[] licBytes) {
            CRC32 crc32 = new CRC32();
            if(userName != null) {
                for(int i = 0; i < userName.length(); ++i) {
                    char var5 = userName.charAt(i);
                    crc32.update(var5);
                }
            }
    
            crc32.update(customerId);
            crc32.update(customerId >> 8);
            crc32.update(customerId >> 16);
            crc32.update(customerId >> 24);
    
            for(int i = 0; i < licBytes.length - 2; i++) {
                byte var6 = licBytes[i];
                crc32.update(var6);
            }
    
            return (short)((int)crc32.getValue());
        }
    • RSA加密,可直接拷贝反编译代码
    • 注冊码生成
      这里写图片描写叙述

    測试注冊码

    眼下的问题是,我们没有私钥。怎么搞?
    能够任意生成一对RSA的公钥/私钥。然后用我们生成的公钥的n,替代class文件里的n,这里的nmodulus.

    看看clion.jar中的com.intellij.a.g.c.class的信息:
    这里写图片描写叙述

    注意右边ASCII字符的部分,上文有提到这个类的n。我们能够替换之。

    怎么替换呢?
    方法非常多,jar、zip、sed,编程方式(javassist、asm、zip)都能够搞定,这个留给读者吧:]


    终于效果:

    这里写图片描写叙述

    clion_crack_succ

  • 相关阅读:
    Python 文件Hash(MD5,SHA1)
    CDHtmlDialog探索Javascript与窗体交互
    C++ 实现不允许继承的类
    (一)JDBC入门及简介
    (二)JDBC 连接数据库
    2012年,总结
    canvas 时钟
    解决数据库日志文件过满的又一方法
    Windows XP 注册表修改大全
    windows2003中安装.netframework1.1
  • 原文地址:https://www.cnblogs.com/yfceshi/p/7057767.html
Copyright © 2020-2023  润新知