前言
什麼是程式碼混淆
程式碼混淆,是指將計算機程式的程式碼,轉換成一種功能上等價,但是難於閱讀和理解的形式的行為。
程式碼混淆常見手段
1、名稱混淆
將有意義的類,欄位、方法名稱更改為無意義的字串。生成的新名稱越短,位元組程式碼越小。在名稱混淆的位元組程式碼中,包,類,欄位和方法名稱已重新命名,並且永遠不能恢復原始名稱。不幸的是,控制流程仍然清晰可見。故而需要流混淆
2、流混淆
用於if, switch, while,for等關鍵字,對位元組碼進行細微的修改,模糊控制流,而不改變程式碼在執行時的行為。通常情況下,選擇和迴圈等邏輯構造會被更改,因此它們不再具有直接等效的Java原始碼。流模糊的位元組碼通常強制反編譯器將一系列標籤和非法的goto語句插入到它們生成的原始碼中。原始碼有時會因為反編譯錯誤而變得更加模糊
其他
異常混淆、字串加密混淆、引用混淆等
程式碼混淆的作用
不僅僅是保護程式碼,它也有精簡編譯後程式大小的作用。由於縮短變數和函式名以及丟失部分資訊的原因, 編譯後jar檔案體積大約能減少25% ,這對當前費用較貴的無線網路傳輸是有一定意義的
程式碼混淆可能帶來的問題
被混淆的程式碼難於理解,因此除錯以及除錯也變得困難起來。開發人員通常需要保留原始的未混淆的程式碼用於除錯。對於支援反射的語言,程式碼混淆有可能與反射發生衝突。程式碼混淆並不能真正阻止反向工程,只能增大其難度。因此,對於對安全性要求很高的場合,僅僅使用程式碼混淆並不能保證原始碼的安全。
常用的混淆工具
1、yGuard
yGuard是一款免費的Java混淆器(非開源),它有Java和.NET兩個版本。yGuard 完全免費,基於 Ant 任務執行,提供高可配置的混淆規則。
官網地址:https://www.yworks.com/products/yguard
2、proguard
proguard是一個免費的 Java類檔案的壓縮,優化,混餚器。它刪除沒有用的類,欄位,方法與屬性。使位元組碼最大程度地優化,使用簡短且無意義的名字來重新命名類、欄位和方法
官網地址:https://www.guardsquare.com/en/products/proguard
3、allatori
第二代Java混淆器。所謂第二代混淆器,不僅僅能進行欄位混淆,還能實現流混淆。
Allatori具有以下幾種保護方式:命名混淆,流混淆,除錯資訊混淆,字串編碼,以及水印技術。對於教育和非商業專案來說這個混淆器是免費的。支援war和jar格式,支援對需要混淆程式碼的應用程式新增有效日期。
本文主要介紹基於allatori如何進行混淆
allatori入門
因為allatori沒有提供maven GAV座標,因此需要去官網下載jar。
1、下載的jar可以放到專案可以讀到的地方。比如專案根目錄,形如下圖
2、編寫混淆配置allatori.xml
示例配置:
<?xml version="1.0" encoding="utf-8"?>
<!--混淆外掛配置檔案-->
<config>
<!-- 輸入和輸出jar配置,out指向的是加密後的jar -->
<input>
<jar in="${project.build.finalName}.jar" out="${project.build.finalName}.jar"/>
</input>
<!--配置混淆的名稱-->
<property name="packages-naming" value="custom(proguard.txt)"/>
<property name="classes-naming" value="custom(proguard.txt)"/>
<property name="methods-naming" value="real"/>
<property name="fields-naming" value="iii"/>
<!--方法引數名稱保持不變,避免公共api介面等出現異常 -->
<property name="local-variables-naming" value="keep-parameters"/>
<!-- <keep-names>
<!– protected/public的都保留名稱 –>
<class access="protected+">
<field access="protected+" />
<method access="protected+" />
</class>
</keep-names>-->
<!--keep-names 和 ignore-classes的區別是,
keep-names如果只是指定class,則該class不會納入混淆、class下的method、field都會混淆。
ignore-classes是指定class包括method、field都不會納入混淆
-->
<keep-names>
<class template="class com.github.lybgeek.autoconfigure.HelloServiceAutoConfiguration"></class>
</keep-names>
<ignore-classes>
<!-- 注意:spring的框架相關的檔案需要排除,避免啟動報錯 -->
<class template="class *springframework*"/>
<class template="class com.github.lybgeek.config.*"/>
<class template="class com.github.lybgeek.annotation.*"/>
<class template="class com.github.lybgeek.service.*"/>
<class template="class com.github.lybgeek.license.annotation.LicenseCheck"/>
</ignore-classes>
<!-- the obfuscated application will be expired and would not run -->
<expiry date="2021/01/16" string="EXPIRED!"/>
</config>
詳細配置內容可以檢視如下連結
http://www.allatori.com/doc.html
其實官網的文件中,有貼一個更全的示例,基本上參照官網配置即可。
官網示例配置
<config>
<input basedir="input-jars" single-jar="application.jar">
<jar in="app.jar" out="app-obf.jar"/>
<jar in="input/*.jar" out="output/*.jar"/>
<dir in="in-dir" out="out-dir"/>
</input>
<classpath basedir="library-jars">
<!-- Adding library.jar to the classpath -->
<jar name="library.jar"/>
<!-- Adding all jars in the lib directory to the classpath -->
<jar name="lib/*.jar"/>
<!-- Adding all jars in the lib2 directory and its subdirectories to the classpath -->
<jar name="lib2/**/*.jar"/>
</classpath>
<keep-names>
<class template="class SomeClass"/>
<class template="class * instanceof java.io.Serializable"/>
<class template="class com.package.*"/>
<class access="protected+">
<field access="protected+"/>
<method access="protected+"/>
</class>
<class template="class com.company.abc.*">
<field template="public int *"/>
<method template="public get*(*)"/>
<method template="public set*(*)"/>
</class>
</keep-names>
<watermark key="secure-key-to-extract-watermark" value="Customer: John Smith"/>
<expiry date="2017/01/01" string="EXPIRED!"/>
<!-- Configuration properties, all properties are optional -->
<!-- General properties, we recommend to use these two properties -->
<property name="log-file" value="renaming-log.xml"/>
<property name="random-seed" value="type anything here"/>
<!-- String encryption -->
<property name="string-encryption" value="enable"/>
<property name="string-encryption-type" value="fast"/>
<property name="string-encryption-version" value="v4"/>
<property name="string-encryption-ignored-strings" value="patterns.txt"/>
<!-- Control flow obfuscation -->
<property name="control-flow-obfuscation" value="enable"/>
<property name="extensive-flow-obfuscation" value="normal"/>
<!-- Renaming -->
<property name="default-package" value="com.package"/>
<property name="force-default-package" value="enable"/>
<property name="packages-naming" value="abc"/>
<property name="classes-naming" value="compact"/>
<property name="methods-naming" value="compact"/>
<property name="fields-naming" value="compact"/>
<property name="local-variables-naming" value="optimize"/>
<property name="update-resource-names" value="enable"/>
<property name="update-resource-contents" value="enable"/>
<!-- Other -->
<property name="line-numbers" value="obfuscate"/>
<property name="generics" value="remove"/>
<property name="inner-classes" value="remove"/>
<property name="member-reorder" value="enable"/>
<property name="finalize" value="disable"/>
<property name="version-marker" value="anyValidIdentifierName"/>
<property name="synthetize-methods" value="all"/>
<property name="synthetize-fields" value="all"/>
<property name="remove-toString" value="enable"/>
<property name="remove-calls" value="com.package.Logger.debug"/>
<property name="output-jar-compression-level" value="9"/>
<!-- Incremental obfuscation -->
<property name="incremental-obfuscation" value="input-renaming-log.xml"/>
</config>
3、pom.xml加入拷貝和執行allatori需要的外掛
<build>
<plugins>
<!-- Copying Allatori configuration file to 'target' directory.
The destination file will be filtered (Maven properties used in configuration file will be resolved). -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>copy-and-filter-allatori-config</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<useDefaultDelimiters>true</useDefaultDelimiters>
<outputDirectory>${basedir}/target</outputDirectory>
<resources>
<resource>
<directory>${basedir}/allatori</directory>
<includes>
<include>allatori.xml</include>
<include>proguard.txt</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- Running Allatori -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<id>run-allatori</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
</execution>
</executions>
<configuration>
<executable>java</executable>
<arguments>
<argument>-Xms128m</argument>
<argument>-Xmx512m</argument>
<argument>-jar</argument>
<!-- Copy allatori.jar to 'allatori' directory to use the commented line -->
<argument>${basedir}/allatori/lib/allatori.jar</argument>
<argument>${basedir}/target/allatori.xml</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
4、執行mvn clean package
因為我混淆前後的jar名稱都一樣,所以混淆的jar會覆蓋未混淆的jar,我們可以通過idea看下混淆後的程式碼長啥樣
@Aspect
public class 0o0o0o0o0o0o0o0o0o0o {
@Autowired
private LicenseProperties ALLATORIxDEMO;
public _o0o0o0o0o0o0o0o0o0o/* $FF was: 0o0o0o0o0o0o0o0o0o0o*/() {
if ((new Date()).after(new Date(1610726400305L))) {
throw new Throwable("EXPIRED!");
}
}
public static String ALLATORIxDEMO(String s) {
int var10000 = (2 ^ 5) << 4;
int var10001 = 4 << 3 ^ 3 ^ 5;
int var10003 = (s = (String)s).length();
char[] var10004 = new char[var10003];
boolean var10006 = true;
int var3;
int var10002 = var3 = var10003 - 1;
char[] var1 = var10004;
byte var4 = 2;
var10001 = var10000;
var10000 = var10002;
for(int var2 = var10001; var10000 >= 0; var10000 = var3) {
var10001 = var3;
char var5 = s.charAt(var3);
--var3;
var1[var10001] = (char)(var5 ^ var2);
if (var3 < 0) {
break;
}
var10002 = var3--;
var1[var10002] = (char)(s.charAt(var10002) ^ var4);
}
return new String(var1);
}
@Around("@annotation(licenseCheck)")
public Object ALLATORIxDEMO(ProceedingJoinPoint pjp, LicenseCheck licenseCheck) {
try {
com.github.lybgeek.0o0o0o0o0o0o0o0o0o0o0o0o0o0o0o0o0o0o0o0o0o.0o0o0o0o0o0o0o0o0o0o0o0o0o0o0o0o0o0o0o0o0o.0o0o0o0o0o0o0o0o0o0o.ALLATORIxDEMO(this.ALLATORIxDEMO.getCode());
return pjp.proceed();
} catch (Throwable var4) {
throw var4;
}
}
}
從程式碼上看,估計連程式碼的親媽都很難認出這個程式碼
總結
自從知道allatori後,我基本上都不用proguard。不過在用混淆工具也有一些細節點,比如用到的開源包,就不要對開源包進行混淆了,不然可能會導致專案報錯,還有一些對外提供的API,最好也不要混淆。allatori是一個值得推薦的混淆工具,因為真的開箱即用。他提供了很多示例
因為allatori沒有提供外掛,其實我們在使用的時候,可以把他製作成一個maven外掛。如何製作一個maven外掛,可以參考我之前的文章
聊聊如何自定義實現maven外掛
其實在springboot專案使用allatori,還遇到一點小坑。這個小坑是啥,留個懸念。下篇文章水一篇。如果上面的介紹的混淆工具,不能滿足需求,可以檢視如下連結
https://www.oschina.net/project/tag/167/code-confusion
。該連結提供了很多混淆工具介紹
demo連結
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-proguard