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版本相同)。 solly 发表于 2019-10-17 21:52
java 支持 AES-256 需要另外两个oracle的jar来处理,去网上搜。。。
{:1_937:}兄弟,,那个JAVA 环境现在可以支持AES-256了的,我替换了包的。。。这个解密, 就是16位一次读取文件来解密,对吧。。我解密错误还是。{:1_936:} 本帖最后由 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 感谢分享,不明觉厉,如果有成品就好了:lol koogg 发表于 2018-11-11 16:55
感谢分享,不明觉厉,如果有成品就好了
只讨论方法,不提供成果 谢谢大神分享方法,小弟明白了。{:1_893:}{:1_921:} 厉害,佩服 大神啊,厉害 过程很详细,可以回去试试,感谢分享注册思路 厉害,谢谢楼主分享! 为将来使用fine report 收藏了先!
厉害,谢谢楼主分享!