关于class加密方案的初步探讨

这个方案还是2020年年初写的,因为公司的产品是基于Java技术开发的,容易被反编译,所以对于class的加密技术开始重视起来。当时南京查疫情特别严格,我每天呆在家里远程办公,开始琢磨一些比较有研究性质一点的东西,就写了这个方案。这个方案也只是给公司开了一个头,后来这个活移交给公司的安全部门去做了。至于现在他们研究到什么程度,我还不得而知。

好了,停止废话,直接贴文档。如果对文档有意见或建议,欢迎前来探讨。另外,我这里也有研究的源码,大家扫码关注一下公众号 智程科技 回复 “class加密” 即可免费领取哦

一.需求

将class文件加密,jvm运行时解密。

二.思路

1.算法(非混淆):采用非对称加密算法,如RSA、ECC(椭圆曲线加密算法),背包算法等等

      优点:安全性更高(公钥+私钥);

      缺点:加解密会影响程序运行效率(程度未知),适合对少量数据进行加密。

2.加密:采用 客户端IDE集成 的方式,对class文件进行加密,加密文件将不能被反编译

3.解密:项目运行期间,对加密的class文件进行解密,且解密方式仅限于内存中完成,不能生成临时文件

三.方案

1.字节码混淆

主要通过将定义的类、变量、方法和包名改为随机字符串,或使用非法字符代替变量符号,添加无用指令等等.只是添加了反编译和对反编译后源码阅读的难度,并不能真正组织反编译.

2.转换本地代码

将java程序像C/C++编译成可执行的二进制代码,但会使java失去跨平台的特性,亦不现实.

3.数字水印技术

在class文件中嵌入以数字水印形式存在的开发者的签名,并不能组织反编译.只能在程序被剽窃时提供证明.

4.自定义类加载器

先将class文件加密处理,再编写自定义类加载器,重载其loadClass方法时,进行解密.但自定义类加载器本身并不能组织被反编译,因此仍不安全.

5.修改java源码方式

自定义的类都是通过AppClassLoader.loadClass(name)进行加载,该方法调用时机由JVM底层决定,返回值被直接加载到JVM内存.其方法中仍调用超类的loadClass()方法.若class文件为加密状态,则该方法则无法解析,报classNotFoundException异常;当默认加载失败后,改用之前自定义的解密方法解密加载.

缺点:非主流方案,发布程序后要带一个捆绑的JRE,且java版本升级会带来隐患.

6. 基于JVMTI和JNI技术进行jvm运行时解密

JVM激活产生第一个类BootstrapClassLoader,其先加载ExtClassLoader(加载JVM系统类),再加载AppClassLoader(加载自定义类)。AppClassLoaderfindclass方法查找需要加载的class文件,调用defineclass方法将class文件的内容转换成Class对象。这个defineclass方法最终调用的是其超类ClassLoader中的本地方法defineclass1(其第2个参数为class文件内容)。

通过Hook机制能够将JVM调用本地方法defineclass1的事件截获,并在Hook函数中利用本地方法实现对class文件内容的解密操作,然后将解密后的字节码传递给JVM。这样就能使解密过程仅在内存中进行,从而实现对加密后的class文件的安全解密。

JVMTI提供的编程接口,允许创建软件代理以监视和控制java编程语言应用程序,即可注册JVMTI_EVENT_CLASS_FILE_LOAD_HOOK事件,让JVM在载入每个class文件时触发回调函数(即解密)。当JVM加载代理时虚拟机调用Agent_OnLoad(jvm与代理交互入口)函数,在Agent_OnLoad函数中对VmInit(jvm初始化完成)事件进行注册,在VmInit回调方法获取JNI环境,通过JNI的RegisterNatives方法将ClassLoader的defineClass1重写以实现解密,通过以上几个函数和事件来完成的,通过编写动态链接库的方式实现以上功能,再由JAVA虚拟机的–agentlib -agentpath参数调用实现的动态链接库。

优点:跨平台,JVMTI/JNI为java自带工具,解密过程完全在内存实现

风险:如spring、tomcat可能会直接读取和验证class文件而未走解密流程,可能需要针对处理,如重新编译spring、Tomcat源码(后续研究);

验证步骤:

1、下载安装VS2010环境,创建动态链接库项目;

2、引入相关jvmti.h文件等;

3、编写方法

    agent_onload方法(添加vmInit监听事件及回调),

    在vmInit回调中获取JNI&JVMTI环境,调用RegisterNatives将defineClass1为自定义代理函数 function B,

    在B中实现解密(需要将加密的class文件解密,即进行与操作,返回class)。

4、项目引入动态链接库,可正确执行

讨论:

1、研究linux可行性;

Window平台从动态链接库中调用函数需要LoadLibrary、GetProcAddress和FreeLibrary库函数,而g++编译器不支持这此库函数,需将windows.h头文件改为dlfcn.h头文件,然后分别使用dlopen、dlsym和dlclose这三个库函数,具体实现和Windows平台类似。利用g++编译代码要使用shared参数,这样将代码编译成动态链接库,利用-o libXXX.so输出代理库,其中输出名称的后缀为lib和so,XXX为自定义名称;程序运行之前,必须使用set命令设置L-D_LIBRARY_PATH变量为代理库的路径;和window平台一样,运行加密class文件时,必须在JVM运行参数通过-agentlib参数指定JVMTI客户端。

2、研究加密算法RSA;

非对称加密算法,同时产生公钥和私钥。

3、JVM启动参数设置?

A、当前在run configuration 中设置运行参数-agentpath:E:\MyDll7.dll;

B、在命令行中java -agentpath:E:\MyDll7.dll  JNI.TestCallDll;

C、在eclipse.ini中配置尚未生效

D、在tomcat中配置jvm运行参数-agentpath,可以显示各类的加载信息,如下图,但是运行中,走自定义的TestCallDll.test()方法时,却没捕获到;可能因为tomcat用自己定义的类加载器,来加载TestCallDll类;可能需要来监听tomcat中加载TestCallDll类的加载器(同时tomcat升级也会有隐患)。springboot同理。

Tomcat启动时,本身就会加载很多类加载器;对于此种情况,可找到其加载器,将加载的操作抛给JVM的classLoader,或者在JVMTI里捕获tomcat自定义类加载器,在其中添加解密代码。

4、设置参数Dll隐藏式加载,放哪儿(系统?);

经测试,Dll文件可置于任何有权限读取的目录中,且保证程序正常运行。

5、加解密方案升级(国外网站/论文)

A、对C++代码进行混淆操作(加大反编译后的阅读难度,但意义不大)

B、给DLL文件加壳处理?

猜你喜欢

转载自blog.csdn.net/weixin_38316944/article/details/114397197
今日推荐