赞
踩
这个方案还是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(加载自定义类)。AppClassLoader中findclass方法查找需要加载的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文件加壳处理?
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。