從零開發基於ASM字節碼的Java代碼混淆插件XHood

項目背景

因在公司負責基礎框架的開發設計,所以針對框架源代碼的保護工作比較重視,之前也加入了一系列保護措施

例如自定義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>

名稱混淆

名稱混淆指的是把類名,方法名,參數名,變量名等定義的名稱進行規則碼替換,以混淆方法名爲例

  • 混淆方法定義

    自定義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表示

    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
    @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();
        }
    }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章