项目背景
由于公司负责基本框架的开发和设计,因此更加重视框架源代码的保护,并增加了一系列保护措施
例如,自定义classloader加密保护、授权license保护等,但都是防君子不防恶棍,安全等级相对较低
在调查了各种加密混淆措施后,决定自行开发混淆插件,独立可控,可根据实际情况定制,实现框架升级后的零感知和零影响。
快速开始项目地址:https://gitee.com/code2roc/xhood
在线文档:https:///code2roc.gitee.io/xhood/#/
将最新发行版下载到本地,执行maven install
工程项目配置maven plugin ,详见在线文档
<build> <plugins> <plugin> <groupId>com.code2roc</groupId> <artifactId>xhood</artifactId> <version>1.0.0</version> <executions> <execution> <goals> <goal>obscure</goal> </goals> <phase>compile</phase> </execution> </executions> <configuration> <!--根包名配置项目 --> <packageName>com.xxx.xxx</packageName> <!--忽略项目启动类别的配置 --> <obscureIgnoreClasss>com.xxx.xxx.Application</obscureIgnoreClasss> </configuration> </plugin> </plugins></build>
方案设计我们首先要清除代码混淆需要实现的是用一系列规则代替原代码的名称结构和内容
达到阅读困难、理解困难、恢复困难的作用
混淆包括方法、成员变量、临时变量、方法参数、常量、类别、包、枚举
这些事项的混淆也需要遵循固定的顺序,因为事项之间仍然存在相互引用的情况
结构混淆(类文件、包名)完成后,需要删除相应的原class文件
混淆前后的效果如下图所示
pom引用方案 <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>9.0</version> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-commons</artifactId> <version>9.0</version> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-util</artifactId> <version>9.0</version> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-tree</artifactId> <version>9.0</version> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-analysis</artifactId> <version>9.0</version> </dependency>
名称混淆名称混淆是指替换类名、方法名、参数名、变量名等定义的名称,以混淆方法名为例
定义混淆方法
自定义Clasvisittor重写visitttttttttttor
过滤枚举的方法
过滤main方法,过滤lambda表达方法,过滤结构函数方法
实现非混淆范围内接口的过滤方法
在非混淆范围内过滤父类的重写方法
调用混淆方法
自定义Methodvisitor重写visitmethodinsn,visitinvokedynamicinsn方法
visitmethodinsn修复混淆方法定义中的方法
visitinvokedynamicinsn修改接口的实现方法和父类的重写方法(混淆范围内和混淆方法定义中的方法)
结构混淆是指对实体class文件和文件夹的重命名,以混淆类名为例
混淆类定义
自定义Clasvisittor重写visitttttttttttor
classs在过滤非混淆范围内
重写visitsource,visitField,visitMethod,visitInnerClass,方法如visitOuterClass等
混淆类定义调用
自定义Methodvisitor重写visitmethodinsn,visitFieldInsn,visitFrame,visitinvokedynamicinsn等方法
混淆类重命名
claswriter定义classwriter获取class文件byte数组重写文件
注释混淆比较特殊,需要继承Annotationvisitor类,重写visit方法才能实现
visitenum方法需要重写注释中的枚举
visitanotion方法需要重写嵌套注释
如果注释中有参数和数组,则需要重写visitArray方法
visitanotation和visitaray方法需要返回annotationvisitor对象。调用super方法后,返回自定义anotationvisitor对象进行递归
混淆规则无论混淆哪一部分,我们总是要根据abc等名称得到一个固定的规则码,比如123
此时,我们会想到与固定输出相对应的固定输入md5的信息摘要算法。
MD5的内容太长了,我们需要截取一些人来简化它。
简化的规则码混淆的内容越多,就越容易碰撞,需要动态调整,简单的递归,最坏的结果是完整的md5表示
public static String getTakeName(String name, int takeLimit, HashMap<String, String> typeMap) { String obscureName = getObscureName(name); if (takeLimit <= 16) { obscureName = obscureName.substring(8, 24); } String takeName = obscureName.substring(0, takeLimit); if (typeMap.containsValue(takeName)) { takeName = getTakeName(name, takeLimit + 1, typeMap); } return takeName; }
注意事项开发在混淆过程中,需要对不同情况进行细节处理,如下所示
- 混淆名称中同一部分的优先级是替换最长的部分
比如方法名Handlemethod和Handle两部分,Handle对应的规则码是123,我先把Handle部分换成123Method和123,那么123Method这种方法混淆后就会定义错误
- 临时变量和方法变量将Methodvisitor的visitlocalvariable方法调用,需要区分
先定义Paramteradapter继承Methodvisitor重写visitparameter记录方法变量
使用事项在springboot项目中,我们需要进行一些配置,以避免项目无法运行或运行错误**
所有需要通过接口返回的实体类别都需要忽略,例如数据库实体DO
通过ConfigurationProperties映射的yml文件配置项需要忽略
通过类名/字段名反射调用的类需要忽略
对于@Aspect注解的切面进行兼容,参照以下写作规则混淆无影响
PointCut注释/类需要指定全名,Around注释指定方法名
@Aspect@Componentpublic class RepeatSubmitAspect { /** * 切面点 指定注解 */ @Pointcut("@annotation(com.code2roc.fastboot.framework.submitlock.SubmitLock) " + "|| @within(com.code2roc.fastboot.framework.submitlock.SubmitLock)") public void repeatSubmitAspect() { } /** * 指定了拦截方法 repeatSubmitAspect */ @Around("repeatSubmitAspect()") public Object around(ProceedingJoinPoint point) throws Throwable { return point.proceed(); }}