benzcomp 发表于 2019-8-22 19:24

FXXXXRXXXXX 10.0 注册分析

本帖最后由 benzcomp 于 2019-8-25 12:09 编辑

这两天发现FR10的加密方式又改了,那就放出以前版本的注册分析,适用于2019.08.16以前版本!

先说结论:
1、授权文件采用RSA加密,理论上无法PJ,要想PJ只能替换密钥。但是,验证过程使用了解密后的明码比较来验证注册码的合法性,所以,理论上可以通过修改验证函数,输入明码license格式文件,跳过RSA加解密过程;
2、FR10再次修改了关键文件加密方式,9.0版使用了100个类加载器递归解密,只有反编译所有的加载器才能知道授权内容。10.0版把解密功能编译成动态库文件了,所有核心文件使用同一密钥,不再递归了。
3、授权核心代码采用全部存储在com.fr.plugin.A目录下,后缀名为classx,这些文件实际上是加密的class文件,部分注册验证文件解密出来class文件不是授权类,是一个描述授权核心类的内文件,这些字符通过javassist工具动态生成类;
4、PJ方法就是修改验证函数所在类,然后,反向加密成classx文件,替换同名文件。

查找注册验证入口请参考关于9.0的帖子(https://www.52pojie.cn/thread-819149-1-1.html),下面只说代码分析部分:
1、负责解密操作的动态库文件在 fine-core-10.0.jar!\com.fr.jvm.assist下

对应不同系统和java环境,\com\fr\jvm\assist 中共有7个,FineAssist.dylib、FineAssist_x86.dll、FineAssist_x86.so、FineAssist_x86_64.dll、FineAssist_x86_64.so、FineAssist_x86_64_ibm.so、FineAssist_x86_ibm.so;
2、JVM的动态调试不会搞,只好用IDA静态分析,所有文件肯定包含一样的解密算法,随意挑一个最小的文件FineAssist_x86.so就可以;
从函数名称可以看出来MyClassFileLoadHook、loadPlainClass、loadSpecialEncryptClass是关于类加载的函数,MyClassFileLoadHook是Hook、loadPlainClass加载普通类、loadSpecialEncryptClas加载特殊类:
int __cdecl MyClassFileLoadHook(_jvmtiEnv *a1, int a2, int a3, int a4, char *s1, int a6, __int64 a7, unsigned __int8 *a8, unsigned __int8 **a9)//*s1 是全路径名, a7 是文件起始地址
{
unsigned __int8 **v10; //
unsigned __int8 **v11; //

if ( !s1 || strncmp(s1, "com/fr/", 7u) || !strncmp(s1, "com/fr/third/", 0xDu) )
    return loadPlainClass(a1, a7, a8, (const unsigned __int8 *)a9, v10);
if ( !strncmp(s1, "com/fr/plugin/engine/", 0x15u) || !strncmp(s1, "com/fr/plugin/A/", 0x10u) )
    return loadSpecialEncryptClass(a1, a7, (const unsigned __int8 *)HIDWORD(a7), (int *)a8, a9);
if ( search((const char **)&LIST, s1) != 0 )
    return loadSpecialEncryptClass(a1, a7, (const unsigned __int8 *)HIDWORD(a7), (int *)a8, a9);
//验证文件前4个字节是否为 不是就调用普通类加载函数loadPlainClass,是就调用loadSpecialEncryptClass特殊类处理函数。
//也就是说所有被加密处理过的文件头都应该是 ,这一点查看classx文件的HEX码也得到了验证。
if ( *(_BYTE *)HIDWORD(a7) != 3 || *(_BYTE *)(HIDWORD(a7) + 1) != 5 || *(_BYTE *)(HIDWORD(a7) + 2) != 7 || *(_BYTE *)(HIDWORD(a7) + 3) != 9 )      
{
    return loadPlainClass(a1, a7, a8, (const unsigned __int8 *)a9, v11);
}
return loadSpecialEncryptClass(a1, a7, (const unsigned __int8 *)HIDWORD(a7), (int *)a8, a9);
}

3、loadSpecialEncryptClass:
特殊类加载,其中 a2 是需要解密的 classx 文件长度,a3 是 classx 的起始地址unsigned int __cdecl loadSpecialEncryptClass(_jvmtiEnv *a1, int a2, const unsigned __int8 *a3, int *a4, unsigned __int8 **a5)
{
void *v5; // esp
unsigned __int8 **v7; //
int *v8; //
const unsigned __int8 *v9; //
_jvmtiEnv *v10; //
int i; //
int j; //
__int64 v13; //
unsigned __int8 ***v14; //
unsigned __int8 *v15; //
unsigned int v16; //

v10 = a1;
v9 = a3;
v8 = a4;
v7 = a5;
v16 = __readgsdword(0x14u);
LODWORD(v13) = a2 - 4;
HIDWORD(v13) = a2 - 5;
v5 = alloca(16 * ((a2 - 5 + 16) / 0x10u));
v14 = &v7;
for ( i = 0; i < (signed int)v13; ++i )
    *((_BYTE *)v14 + i) = v9;    //v9=a3(classx的地址)从第5字节开始,存入v14,从上一步可以知道前4字节只是加密文件标记,不参与解密计算
*v8 = v13;
_jvmtiEnv::Allocate(v10, (signed int)v13, v7);
v15 = *v7;
for ( j = 0; j < (signed int)v13; ++j )
    v15 = *((_BYTE *)v14 + j);
x(v15, v13);   //x函数就是解密过程
return __readgsdword(0x14u) ^ v16;
}
4、解密函数,其实就是个字节异或处理a1是加密数据,a2是密钥(8字节)
int __cdecl x(unsigned __int8 *a1, int a2)
{
int result; // eax
int i; //

for ( i = 0; ; ++i )
{
    result = i;
    if ( i >= a2 )
      break;
    a1 ^= T;//每8个字节为一组
}
return result;
}
至此,就已经完成classx到class的解密了。
5、从以上分析知道,解密classx文件的步骤其实很简单:从classx文件的第5字节开始,8字节为一组,与异或密钥数组计算,就可以得到解密数据。
那么,问题来了,这个异或密钥是什么?
大家知道标准的class头文件前8个字节是固定的 ,所以,只要把classx的第5-12字节拿来逐字节异或运算一下就可以得到密钥了。

6、注册码验证在com.fr.plugin.A.N.A.C.B.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文件,这里拼接出的class文件,其实还是加密过得classx格式文件,再进行一次解密才能得到真正的class。
7、反编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 就可以跳过RSA解密,直接使用明码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;
}
}
8、通过class二进制编辑器修改com.fr.license.selector.EncryptedLicenseSelector.class,或者你自己编译一个com.fr.license.selector.EncryptedLicenseSelector.class文件,按照解密的反向步骤:加密com.fr.license.selector.EncryptedLicenseSelector.class——用加密过的com.fr.license.selector.EncryptedLicenseSelector.class的字节流数组替换com.fr.plugin.A.N.A.C.B.class对应代码——加密成classx——替换com.fr.plugin.A.N.A.C.B.classx——破解完成。
9、用如下内容的注册文件注册即可
{"VERSION":"10.0","DEADLINE":4102444799499,"CONCURRENCY":"0"}

wcb0414 发表于 2019-8-22 21:02

        public static byte[] Xor(byte[] data, byte[] key)
        {
                for (int i = 0;; i++) {
                        if (i >= data.length)
                                break;
                        data ^= key;
                }
                return data;
        }

{:1_918:}{:1_918:}{:1_918:}{:1_918:}

wapj152321 发表于 2019-8-22 20:10

什么情况

heiketian10 发表于 2020-2-24 16:35

本帖最后由 heiketian10 于 2020-9-9 13:14 编辑

新版本不错

alicc 发表于 2019-8-22 20:27

说给自己听列?这样也可以灌水?

benzcomp 发表于 2019-8-22 20:58

临时有事,还没编辑完,明天再发

3yu3 发表于 2019-8-22 21:14

这个软件加密变来变去,也真是奇葩。期待发出分析 结果。。。

大象叔叔 发表于 2019-8-22 21:28

坐等更新哦,楼主{:1_893:}

狐狸爱葡萄 发表于 2019-8-22 22:29

楼主能给个9的破解版吗。目前只有无限试用版

solly 发表于 2019-8-23 02:04

8月份版本改成 AES-256 加密了,但是每个文件留了一个小尾巴,算是一个小陷阱吧。

scientist 发表于 2019-8-23 05:36

Sounds interesting ...
Let's see what's going on there.
页: [1] 2 3 4 5 6
查看完整版本: FXXXXRXXXXX 10.0 注册分析