从零开发基于ASM字节码的Java代码混淆插件XHood

项目背景

因在公司负责基础框架的开发设计,所以针对框架源代码的保护工作比较重视,之前也加入了一系列保护措施

例如自定义classloader加密保护,授权license保护等,但都是防君子不防小人,安全等级还比较低

经过调研各类加密混淆措施后,决定自研混淆插件,自主可控,能够贴合实际情况进行定制化,达到框架升级后使用零感知,零影响。

快速开始

项目地址:gitee.com/code2roc/xh...

在线文档:code2roc.gitee.io/xhood/#/

  • 下载最新发行版到本地,执行maven install

  • 工程项目配置maven plugin ,详细配置见在线文档

xml 复制代码
<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引用

xml 复制代码
  		<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>

名称混淆

名称混淆指的是把类名,方法名,参数名,变量名等定义的名称进行规则码替换,以混淆方法名为例

  • 混淆方法定义

    自定义ClassVisitor重写visit方法

    过滤枚举类的方法

    过滤main方法,过滤lambda表达式方法,过滤构造函数方法

    过滤非混淆范围内接口的实现方法

    过滤非混淆范围内父类的重写方法

  • 混淆方法调用

    自定义MethodVisitor重写visitMethodInsn,visitInvokeDynamicInsn方法

    visitMethodInsn修混淆方法定义中的方法

    visitInvokeDynamicInsn修改接口的实现方法和父类的重写方法(混淆范围内且混淆方法定义中的方法)

结构混淆

结构混淆指的是修改类名,包名时对实体class文件和文件夹的重命名,以混淆类名为例

  • 混淆类定义

    自定义ClassVisitor重写visit方法

    过滤非混淆范围内的class

    重写visitSource,visitField,visitMethod,visitInnerClass,visitOuterClass等方法

  • 混淆类定义调用

    自定义MethodVisitor重写visitMethodInsn,visitFieldInsn,visitFrame,visitInvokeDynamicInsn等方法

  • 混淆类重命名

    定义ClassWriter获取class文件byte数组重新写入文件

注解混淆

注解的混淆比较特殊,需要继承AnnotationVisitor类来重写visit方法实现

针对注解中有枚举需要重写visitEnum方法

针对嵌套注解需要重写visitAnnotation方法

针对注解有参数有数组的,需要重写visitArray方法

visitAnnotation和visitArray方法需要返回AnnotationVisitor对象,调用super方法后返回自定义AnnotationVisitor对象递归处理即可

混淆规则

无论混淆哪一部分,我们总是要根据一个名称例如abc混淆后得到一个固定的规则码例如123

这时候我们会想到md5这种固定输入对应固定输出的信息摘要算法

md5内容太长,我们需要截取某几位进行简化

简化后的规则码在待混淆内容越多时越容易碰撞,需要需要动态调整,简单递归即可,最坏结果就是完整的md5表示

java 复制代码
    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注解指定方法名

    java 复制代码
    @Aspect
    @Component
    public 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();
        }
    }
相关推荐
知兀7 分钟前
【MybatisPlus】后端用枚举类,数据库用tinyint,存在枚举类型转换
java
StockTV9 分钟前
印度股票实时数据 NSE和BSE的实时行情、K 线及指数数据
java·开发语言·spring boot·python
User_芊芊君子11 分钟前
【OpenAI 把 AI 玩明白了】:自主推理 + 动态知识图谱,这 4 个技术突破要颠覆行业
java·人工智能·知识图谱
c++之路44 分钟前
C++20概述
java·开发语言·c++20
Championship.23.241 小时前
Linux Top 命令族深度解析与实战指南
java·linux·服务器·top·linux调试
橘子海全栈攻城狮1 小时前
【最新源码】养老院系统管理A013
java·spring boot·后端·web安全·微信小程序
逻辑驱动的ken1 小时前
Java高频面试考点18
java·开发语言·数据库·算法·面试·职场和发展·哈希算法
冷雨夜中漫步2 小时前
Claude Code源码分析——Claude Code Agent Loop 详细设计文档
java·开发语言·人工智能·ai
直奔標竿2 小时前
Java开发者AI转型第二十六课!Spring AI 个人知识库实战(五)——联网搜索增强实战
java·开发语言·人工智能·spring boot·后端·spring
one_love_zfl2 小时前
java面试-微服务组件篇
java·微服务·面试