項目背景
因在公司負責基礎框架的開發設計,所以針對框架源代碼的保護工作比較重視,之前也加入了一系列保護措施
例如自定義classloader加密保護,授權license保護等,但都是防君子不防小人,安全等級還比較低
經過調研各類加密混淆措施後,決定自研混淆插件,自主可控,能夠貼合實際情況進行定製化,達到框架升級後使用零感知,零影響。
快速開始
項目地址:https://gitee.com/code2roc/xhood
在線文檔:https://code2roc.gitee.io/xhood/#/
<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(); } }