吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 19189|回复: 52
收起左侧

[原创] FXXXXRXXXXX 10.0 注册分析

[复制链接]
benzcomp 发表于 2019-8-22 19:24
本帖最后由 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下

FineAssist

FineAssist

对应不同系统和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加载特殊类:
[C] 纯文本查看 复制代码
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; // [esp-Ch] [ebp-14h]
  unsigned __int8 **v11; // [esp-Ch] [ebp-14h]

  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个字节是否为 [03 05 07 09] 不是就调用普通类加载函数loadPlainClass,是就调用loadSpecialEncryptClass特殊类处理函数。
//也就是说所有被加密处理过的文件头都应该是 [03 05 07 09],这一点查看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 的起始地址
[C] 纯文本查看 复制代码
unsigned int __cdecl loadSpecialEncryptClass(_jvmtiEnv *a1, int a2, const unsigned __int8 *a3, int *a4, unsigned __int8 **a5)
{
  void *v5; // esp
  unsigned __int8 **v7; // [esp+0h] [ebp-38h]
  int *v8; // [esp+4h] [ebp-34h]
  const unsigned __int8 *v9; // [esp+8h] [ebp-30h]
  _jvmtiEnv *v10; // [esp+Ch] [ebp-2Ch]
  int i; // [esp+14h] [ebp-24h]
  int j; // [esp+18h] [ebp-20h]
  __int64 v13; // [esp+1Ch] [ebp-1Ch]
  unsigned __int8 ***v14; // [esp+24h] [ebp-14h]
  unsigned __int8 *v15; // [esp+28h] [ebp-10h]
  unsigned int v16; // [esp+2Ch] [ebp-Ch]

  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[i + 4];    //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[j] = *((_BYTE *)v14 + j);
  x(v15, v13);   //x函数就是解密过程
  return __readgsdword(0x14u) ^ v16;
}

4、解密函数,其实就是个字节异或处理a1是加密数据,a2是密钥(8字节)
[C] 纯文本查看 复制代码
int __cdecl x(unsigned __int8 *a1, int a2)
{
  int result; // eax
  int i; // [esp+Ch] [ebp-Ch]

  for ( i = 0; ; ++i )
  {
    result = i;
    if ( i >= a2 )
      break;
    a1[i] ^= T[i % 8];  //每8个字节为一组
  }
  return result;
}

至此,就已经完成classx到class的解密了。
5、从以上分析知道,解密classx文件的步骤其实很简单:从classx文件的第5字节开始,8字节为一组,与异或密钥数组计算,就可以得到解密数据。
那么,问题来了,这个异或密钥是什么?
大家知道标准的class头文件前8个字节是固定的 [CA FE BA BE 00 00 00 31],所以,只要把classx的第5-12字节拿来逐字节异或运算一下就可以得到密钥了。

6、注册码验证在com.fr.plugin.A.N.A.C.B.classx(不同版本的位置可能会不同),解密得到class文件,反编:
[Java] 纯文本查看 复制代码
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[0];
                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验证入口:
[Java] 纯文本查看 复制代码
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[0];
  }
   
  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[0];
    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、用如下内容的注册文件注册即可
[XML] 纯文本查看 复制代码
{"VERSION":"10.0","DEADLINE":4102444799499,"CONCURRENCY":"0"}

免费评分

参与人数 5威望 +1 吾爱币 +11 热心值 +4 收起 理由
一叶青 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
rox + 1 + 1 谢谢@Thanks!
gordonchan + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Hmily + 1 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
erguotou + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

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[i] ^= key[i % 8];
                }
                return data;
        }

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
坐等更新哦,楼主
狐狸爱葡萄 发表于 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.
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 23:04

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表