好友
阅读权限 20
听众
最后登录 1970-1-1
本帖最后由 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
对应不同系统和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
用心讨论,共获提升!
查看全部评分