benzcomp 发表于 2018-11-11 16:52

FXXXXRXXXXX 9.0 注册分析

本帖最后由 ubuntu 于 2019-6-6 18:32 编辑

先说结论:
1、授权文件采用RSA加密,理论上是无法破解,要想破解只能替换密钥。但是,验证过程使用了解密后的明码比较来验证注册码的合法性,所以,理论上可以通过修改验证函数,输入明码license格式文件,跳过RSA加解密过程;
2、FineReport9为了保证安全,软件自定义了类加载器,设计了100个类加载器,只有反编译所有的加载器才能知道授权内容,过程大致如下:通过com.fr.plugin.bridge. Start推出com.fr.plugin.bridge._7c269529_9bce_43a5_95db_b44008bf984f,然后通过com.fr.plugin.bridge._7c269529_9bce_43a5_95db_b44008bf984f推出下一个加载器,一直往下推,总共有100个。
3、授权核心代码采用全部存储在com.fr.plugin.A目录下,后缀名为classx,这些文件实际上是加密的class文件,这些文件解密出来class文件不是授权类,是一个描述授权核心类的内文件,这些字符通过javassist工具动态生成类;
4、修改验证函数所在类com.fr.plugin.A.H.D,然后,反向加密成classx文件,替换同名文件。
分析步骤:
1、因为FineReport9的加密方式完全改变了,所以,8的方式不管用了。反编fr-core-9.0.jar,搜license可以在fr-core-9.0.jar!\com\fr\regist\FRCoreContext.class中找到关于license的函数
static
{
    LISTENERS = new CopyOnWriteArrayList();
    PluginStartup.start();
    refresh();
    addShutDownHook();
}
PluginStartup.start();这一句是真正调用license相关函数的入口,跟到fr-core-9.0.jar!\com\fr\plugin\manage\PluginStartup.class,内容如下:
package com.fr.plugin.manage;

public abstract class PluginStartup
{
private static final String START_LOADER_FACTORY_NAME = "com.fr.plugin.bridge.Start";
private static final int STATE_NONE = 0;
private static final int STATE_STARTING = 1;
private static final int STATE_STATED = 2;
private static int state = 0;

public static synchronized void start()
{
    if (state != 0) {
      return;
    }
    state = 1;
    try
    {
      initEncryptedBridge();
    }
    catch (Throwable localThrowable1)
    {
      try
      {
      Class.forName("com.fr.plugin.bridge.FinePluginBridge");
      }
      catch (Throwable localThrowable2)
      {
      localThrowable2.printStackTrace();
      }
    }
    state = 2;
}

private static void initEncryptedBridge()
    throws Exception
{
    EngineClassLoaderFactory localEngineClassLoaderFactory = (EngineClassLoaderFactory)Class.forName("com.fr.plugin.bridge.Start").newInstance();
    ClassLoader localClassLoader = localEngineClassLoaderFactory.create();
    Class.forName("com.fr.plugin.bridge.FinePluginBridge", true, localClassLoader);
}
}
可以看出来,加载了com.fr.plugin.bridge.Start,fr-core-9.0.jar!\com\fr\plugin\bridge\Start.class:
public class Start
extends ClassLoader
implements EngineClassLoaderFactory
{
private static final String NAME = "com.fr.plugin.bridge._ef95c0db_2556_4e47_a048_ea1b1a9c89a3";
private static final String CLASS_PATH = "com/fr/plugin/bridge/_ef95c0db_2556_4e47_a048_ea1b1a9c89a3.classx";

public Start()
{
    super(GeneralContext.class.getClassLoader());
}

public ClassLoader create()
    throws Exception
{
    return decryptFactory().create();
}

private EngineClassLoaderFactory decryptFactory()
    throws Exception
{
    byte[] arrayOfByte1 = PluginBaseUtils.inputStream2Bytes(super.getResourceAsStream("com/fr/plugin/bridge/_ef95c0db_2556_4e47_a048_ea1b1a9c89a3.classx"));
    try
    {
      Field localField = Unsafe.class.getDeclaredField("theUnsafe");
      localField.setAccessible(true);
      Unsafe localUnsafe = (Unsafe)localField.get(null);
      
      byte[] arrayOfByte2 = _8ba75880_050f_43a5_9789_4afd24c549ef().doFinal(arrayOfByte1);
      Class localClass = localUnsafe.defineClass("com.fr.plugin.bridge._ef95c0db_2556_4e47_a048_ea1b1a9c89a3", arrayOfByte2, 0, arrayOfByte2.length, this, null);
      
      return (EngineClassLoaderFactory)localClass.newInstance();
    }
    catch (Throwable localThrowable)
    {
      throw new ClassNotFoundException("");
    }
}

private Cipher _8ba75880_050f_43a5_9789_4afd24c549ef()
    throws Exception
{
    Cipher localCipher = Cipher.getInstance("AES");
   
    byte[] arrayOfByte1 = { -83, 120, 8, 6, -104, 96, -80, -98, 109, 56, -117, 118, 30, 18, -119, 7 };
   
    byte[] arrayOfByte2 = arrayOfByte1;
   
    SecretKeySpec localSecretKeySpec = new SecretKeySpec(arrayOfByte2, "AES");
   
    localCipher.init(2, localSecretKeySpec);
   
    return localCipher;
}
}

从这里开始,就是不停的使用AES解密下一个类,动态加载,再解密下一个,一共100个classx文件,每一个文件的解密密钥不同。直到第100个classx解密后就可以得到com/fr/plugin/A目录下所有classx文件的密钥。
2、得到密钥后把com.fr.plugin.A.M.A.C.D.classx解密得到class文件,反编:
class DynaClass {
public static String D() {
                try {
                        return new String(new byte[] { (byte) 99, (byte) 111, (byte) 109, (byte) 46, (byte) 102, (byte) 114,
                                        (byte) 46, (byte) 108, (byte) 105, (byte) 99, (byte) 101, (byte) 110, (byte) 115, (byte) 101,
                                        (byte) 46, (byte) 115, (byte) 101, (byte) 108, (byte) 101, (byte) 99, (byte) 116, (byte) 111,
                                        (byte) 114, (byte) 46, (byte) 69, (byte) 110, (byte) 99, (byte) 114, (byte) 121, (byte) 112,
                                        (byte) 116, (byte) 101, (byte) 100, (byte) 76, (byte) 105, (byte) 99, (byte) 101, (byte) 110,
                                        (byte) 115, (byte) 101, (byte) 83, (byte) 101, (byte) 108, (byte) 101, (byte) 99, (byte) 116,
                                        (byte) 111, (byte) 114 }, "UTF-8");
                } catch (Throwable localThrowable) {
                }
                return "";
      }

      public static byte[] E() {
                byte[] arrayOfByte = new byte;
                arrayOfByte = A(arrayOfByte, getByteCode0());
                arrayOfByte = A(arrayOfByte, getByteCode1());
                arrayOfByte = A(arrayOfByte, getByteCode2());
                arrayOfByte = A(arrayOfByte, getByteCode3());
                arrayOfByte = A(arrayOfByte, getByteCode4());
                return arrayOfByte;
      }

      public String A() {
                return "dc1473c8b718c3f144d3f443517d2c44";
      }

      private static byte[] getByteCode0() {
                return new byte[] { (byte) -54,…… };
      }

      private static byte[] getByteCode1() {
                return new byte[] { (byte) 101, ……};
      }

      private static byte[] getByteCode2() {
                return new byte[] { (byte) -126, …… };
      }

      private static byte[] getByteCode3() {
                return new byte[] { (byte) 47, …… };
      }

      private static byte[] getByteCode4() {
                return new byte[] { (byte) 0, …… };
      }
}
通过byte字节数组拼接出com.fr.license.selector.EncryptedLicenseSelector.class文件
3、反编com.fr.license.selector.EncryptedLicenseSelector.class就到了真正的license验证入口:
public abstract class EncryptedLicenseSelector
extends AbstractLicenseSelector
{
private static final String _0eca828a_77d6_428a_9a97_91c04b434f4a = "RSA";
private static final int _9703cf65_25a0_49a0_91ed_738941a0738d = 128;
private static final ExecutorService _2e5864b3_2147_4738_9fbf_edfc852e40ae = ;

byte[] getBytes()
{
    byte[] arrayOfByte = readRawBytes();
    return decrypt(arrayOfByte);      //改成 return arrayOfByte 就可以使用明码lic文件验证了。
}

private byte[] decrypt(byte[] paramArrayOfByte)
{
    try
    {
      System.getProperties().setProperty("com.ibm.crypto.provider.DoRSATypeChecking", "false");
      X509EncodedKeySpec localX509EncodedKeySpec = new X509EncodedKeySpec(LicenseConstants.KEY);
      KeyFactory localKeyFactory = KeyFactory.getInstance("RSA");
      PublicKey localPublicKey = localKeyFactory.generatePublic(localX509EncodedKeySpec);
      Cipher localCipher = Cipher.getInstance("RSA");
      localCipher.init(2, localPublicKey);
      return dealFragment(paramArrayOfByte, localCipher);
    }
    catch (Throwable localThrowable)
    {
      _2e5864b3_2147_4738_9fbf_edfc852e40ae.execute(new EncryptedLicenseSelector.1(this));
      decryptFailed();
    }
    return new byte;
}

protected void decryptFailed()
{
    FRLoggerFactory.getLogger().error("Read license failed.");
}

abstract byte[] readRawBytes();

private static byte[] dealFragment(byte[] paramArrayOfByte, Cipher paramCipher)
    throws IllegalBlockSizeException, BadPaddingException
{
    byte[] arrayOfByte1 = new byte;
    for (int i = 0; i < paramArrayOfByte.length; i += 128)
    {
      byte[] arrayOfByte2 = ArrayUtils.subarray(paramArrayOfByte, i, i + 128);
      byte[] arrayOfByte3 = paramCipher.doFinal(arrayOfByte2);
      arrayOfByte1 = ArrayUtils.addAll(arrayOfByte1, arrayOfByte3);
    }
    return arrayOfByte1;
}
}
4、通过class二进制编辑器修改com.fr.license.selector.EncryptedLicenseSelector.class,或者你自己编译一个com.fr.license.selector.EncryptedLicenseSelector.class文件,按照解密的反向步骤:
用com.fr.license.selector.EncryptedLicenseSelector.class的字节流数组替换com.fr.plugin.A.M.A.C.D.class对应代码——编译生成com.fr.plugin.A.M.A.C.D.class——加密成classx——替换com.fr.plugin.A.M.A.C.D.classx——破解完成。
5、用如下内容的注册文件注册即可:
{"VERSION":"9.0","DEADLINE":4102444799499,"CONCURRENCY":"0"}

还有一种替换官方RSA公钥的破解方式:
解密classx文件的步骤一样不能少,只需要修改保存RSA公钥的classx文件com.fr.plugin.A.M.A.Y.classx(不同版本文件名可能不尽相同),动态加载成后生成/com/fr/license/selector/LicenseConstants.class,改成你自己的RSA密钥,然后,反向加密成classx文件。注册时,需要按照FineReport的license文件加密格式创建注册文件(与8.0版本相同)。

gyhixq 发表于 2019-10-18 10:50

solly 发表于 2019-10-17 21:52
java 支持 AES-256 需要另外两个oracle的jar来处理,去网上搜。。。

{:1_937:}兄弟,,那个JAVA 环境现在可以支持AES-256了的,我替换了包的。。。这个解密, 就是16位一次读取文件来解密,对吧。。我解密错误还是。{:1_936:}

guoyangye 发表于 2019-3-28 22:20

本帖最后由 guoyangye 于 2019-5-4 19:23 编辑

solly 发表于 2019-3-17 21:05
com.fr.plugin.A.M.A下二次要解密,看看 com.fr.plugin.A.M.A.E.class,以及 com.fr.plugin.A.M.A.A,com ...
最近一直没时间看,刚才看了一下,解密了A.M.A.A、A.M.A.B、A.M.A.C下的所有文件,A.M.A.A基本都是general内的东西,A.M.A.B基本都是插件有关的东西,A.M.A.C下面都是license相关的东西,死活没看到e.class,后来才发现,真坏啊,分别有e.class和E.class

koogg 发表于 2018-11-11 16:55

感谢分享,不明觉厉,如果有成品就好了:lol

benzcomp 发表于 2018-11-11 16:59

koogg 发表于 2018-11-11 16:55
感谢分享,不明觉厉,如果有成品就好了

只讨论方法,不提供成果

xdnice 发表于 2018-11-11 17:41

谢谢大神分享方法,小弟明白了。{:1_893:}{:1_921:}

勇敢做自己 发表于 2018-11-11 19:37

厉害,佩服

vaysalee 发表于 2018-11-11 21:20

大神啊,厉害

vruc 发表于 2018-11-11 22:43

过程很详细,可以回去试试,感谢分享注册思路

sailor291 发表于 2018-11-12 07:53

厉害,谢谢楼主分享!

leaou 发表于 2018-11-12 07:58

为将来使用fine report 收藏了先!

kzt_cn 发表于 2018-11-12 08:06


厉害,谢谢楼主分享!
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: FXXXXRXXXXX 9.0 注册分析