本文不讨论怎样进行破解,只是叙述一下研究加密过程的各个步骤
0.插件加密
从FineReport9.0之后,所有的插件在打包过程中会被加密,如果直接解压打包后生成的jar包,仍然可以得到class文件,但是这些class文件是加密过的,无法用常见的Decompiler软件打开。
破解的思路有两种,一种是寻找FineReport加载插件所使用的ClassLoader,这其中必然有对Class文件解密的过程,还有一种就是寻找在插件打包的过程中,是怎样对插件进行加密的,本文采用的是第二种。
1.从Ant构建配置文件开始
下面是构建文件(build.xml)的一部分,关键就在这部分:
1 | <taskdef name="pretreatment" classname="com.fr.plugin.pack.PluginPretreatmentTask"> |
我们可以看到在使用Ant构建的过程中,使用了com.fr.plugin.pack.PluginPretreatmentTask这个类进行预处理,并传入了一个baseDir的参数,这个类位于fr-core这个jar包中
2.PluginPretreatmentTask
- 在PluginPretreatmentTask的execute函数中,关键是
PluginStartup.start()
(加载进特殊的ClassLoader)和PluginManager.pretreatment(this.config)
(对插件编译后的Class二进制数据进行加密等其他操作),
3.加载ClassLoader
- 在
PluginStartup.start()
中关键是initEncryptedBridge函数,其中将bridge包中的Start类加载进来,并使用新的ClassLoader加载FinePluginBridge
1 | private static void initEncryptedBridge() throws Exception { |
- 在Start这个类中,主要做的事一是将bridge包中的一个classx文件加载进来,并对他进行AES的解密,解密后的数据就是EngineClassLoaderFactory“的一个子类,对其调用create方法
1 | public ClassLoader create() throws Exception { |
- 如果将”_4c1a49e2_0fcf_443f_94e3_f1c53998b507.classx”文件解密并反编译,会发现生成一个与Start十分类似的类。因此,它就使用了这种方法将最终的ClassLoader使用不同的AES密钥加密了100层。这个ClassLoader的逻辑类似,同样是当类属于特定的包时,使用AES对其二进制数据进行解谜。
1 | // 该ClassLoader只对下面这些包中的类起作用 |
4.FinePluginBridge
插件加密的关键代码如下:
1 | public void pretreatment(PluginPretreatmentConfig var1) throws Exception { |
这里有一点有趣的就是,如果我们阅读com.fr.plugin.A.H.A.L().A(var1)
里面的预处理过程会发现,它调用了一个com.fr.plugin.A.H.D
的类,但这个类的class文件并没有出现在我们解压后的fr-core包中。他这里运用到了ClassLoader的缓存加载数据的特点,即会先寻找某个类是否已经loaded,在A.B().A()
过程中就将这个com.fr.plugin.A.H.D
类加载进内存
5.预加载关键类
阅读A.B().A()
的代码发现,加载类的关键代码位于com.fr.plugin.A.M.A.B
, E()的结果是类的数据, D()的结果是类的包名加类名:
1 | private void A(ClassLoader classLoader) { |
这段代码对每一个实现了相同接口的类调用了该过程将其加载进内存,每一个类的形式如下所示,其中的A方法就是将传入的两个字节数组拼接:
1 | "getName") (A = |
我们将这些类加载后,就会发现com.fr.plugin.A.H.D
这个类了。
6.加密过程
加密过程很简单,就是在打包过程中,使用随机生成的AES密钥对源码Class文件进行加密,并将密钥加密后存在打包过后的某个文件中。