115
技術社區[雲棲]
混淆小助手-Obfuscator
涉及的技術:
1.自定義Gradle plugin
2.Jitpack發布開源庫
3.Proguard代碼混淆
4.Android Gradle編譯
Github地址:
Obfucator源碼
obfuscator-plugin源碼
一.為什麼要做混淆
Android是java開發的, 所以很容易被反編譯.為了提升app的安全等級, 需要對app進行混淆. 常用有3種處理方式:
- proguard代碼混淆
- dexguard
- apk 加殼
現在市麵上有很多加殼平台,可以防止app被反編譯.
但是這不是萬能的,因為脫殼技術也日益更新,如果app脫殼成功,代碼就可以全被看到了,所以很有必要對代碼進行proguard混淆. 這可以
1).可以減少apk的大小;
2)加大反編譯後讀取代碼邏輯的難度.
二.不能混淆的幾種case
app中有些代碼是不能混淆的,所以我們要編寫keep配置,讓他保持不混淆
例如,保持com.xiaomi
包下的類不被混淆
-keep class com.xiaomi.**{*;}
常見不能混淆的2種case:
- 反射調用到的類或者變量
- 用於解釋Json數據的bean
比較經典的是ObjectAnimator
.
像下麵例子, ObjectAnimator
會通過反射調用setNumber
. 如果setNumber
被混淆了ObjectAnimator
就不起作用了.
ObjectAnimator.ofObject(this, "number", new TypeEvaluator<Long>() {
@Override
public Long evaluate(float fraction, Long startValue, Long endValue) {
...
}
}, number);
//這裏的setNumber是不能混淆的.
//如果混淆了,ObjectAnimator就不起作用了
public void setNumber(Long number) {
}
像上麵示例,我們要保持setNumber方法不被混淆會比較麻煩. 一般為了圖方便, 直接就讓整個類都不被混淆.
三. 痛點
入上麵ObjectAnimator示例,現在配置混淆有2個痛點:
- 配置粒度較細的混淆很困難
- 因為寫混淆配置麻煩, 所以混淆一般都是寫完一部分代碼後, 再寫混淆配置. 這造成寫配置的時候都不記得哪些需要加上混淆配置了.
一.混淆助手Obfuscator
混淆小助手Obfuscator可以:
- 控製粒度很細的混淆
- 寫代碼的時候,可以很方便的隨手寫上混淆.
實現原理:
實現思路是利用"注解/接口"控製混淆, 注解的優勢是寫代碼可以隨手寫上; 接口的優勢是類可以實現多個接口.
場景: 保持某個 "方法/變量" 不被混淆
回到上麵ObjectAnimator的例子:
一共用3步實現方法不被混淆
1.先聲明了一個注解 ObfuscateKeepThisField
@Inherited
public @interface ObfuscateKeepThisField {
}
2.在proguard-rules.pro
中添加配置
#保留帶注釋的成員,適用於類和內部類
#對應聲明ObfuscateKeepThisField
-keepclassmembers class * {
@com.helen.obfuscator.ObfuscateKeepThisField * ;
}
3.在setNumber
上加上此注解
@ObfuscateKeepThisField
public void setNumber(Long number) {
}
這裏步驟1和2 在整個App中做一次, 後續整個App中的所有類都可以用ObfuscateKeepThisField
進行處理了.
Obfuscator現在支持:
- 類中所有public成員不混淆(不包括內部類)
- 類中所有成員不混淆(不包括內部類)(內部類如果不需要混淆也要加入此注釋)
- 保留所有實現IObfuscateKeepAll接口的類,(注意接口有傳遞性,他的子類也會被keep)(內部類如果沒有繼承此接口會被混淆)
- 保留帶注釋的成員,適用於類和內部類
- 保留類中set/get/is函數
二.發布到Jitpack
把前麵聲明的注解發布到Jitpack上, 這樣所有項目都可以複用. 過程很簡單,幾分鍾完事.
Jitpack發布開源庫
三.編寫 Gradle plugin
上麵的混淆需要手動把混淆配置拷貝到 proguard-rules.pro
, 這個手動的步驟也略麻煩. Gradle plugin功能這麼強大, 我們來利用 編譯插件來自動完成 混淆配置.
自定義Plugin方法見文章自定義Gradle plugin
對於obfuscator-plugin,需要解決3個問題:
- 在"Android App&libaray"中才生效
- 把Jitpack中obfuscator的依賴自動加進來
- 生成混淆配置,並且讓其生效
下麵分析一下實現:
1.判斷是否是App或者Library
我們的App或者Library,都需要在build.gradle中聲明
比如App的是
apply plugin: 'com.android.application'
如果是Library,我們會聲明
apply plugin: 'com.android.library'
所以判斷當前module類型就很簡單了,通過判斷project的plugin裏有沒有AppPlugin或者LibraryPlugin來實現.
Project project;
/**
* 是否含有 Android application plugin
* @return true:有
*/
boolean hasApp() {
return project.plugins.withType(AppPlugin);
}
/**
* 是否含有 Android lib plugin
* @return true:有
*/
boolean hasLib() {
return project.plugins.withType(LibraryPlugin);
}
2. 加入obfuscator相關依賴
Gradle plugin 做這個事很簡單, 他會傳給Plugin一個參數project. 用它就可以加入各種依賴.
@Override
void apply(Project project) {
//添加倉庫
project.repositories{
//obfuscator發布在 jitpack上.
//所以需要依賴jitpack的maven倉庫
maven { url 'https://jitpack.io' }
}
//加入項目依賴
project.dependencies {
//引入obfuscator的注解聲明
compile 'com.github.helen-x:obfuscator:1.0'
}
}
3.加入混淆配置
我們編譯時係統會要求聲明BuildType, 比如release, debug.
BuildType是Gradle的一個編譯類. 從他的代碼可以看到proguard配置文件是一個List.我們可以通過 proguardFile() 函數加入新的Proguard配置.
//BuildType 類
public class BuildType extends DefaultBuildType implements CoreBuildType, Serializable {
@NonNull
public BuildType proguardFile(@NonNull Object proguardFile) {
getProguardFiles().add(project.file(proguardFile));
return this;
}
}
那我們需要做的是:
- 在buildType中加入Proguard
- 在合適的情況下生成proguard文件
在buildType中加入Proguard
project.android.buildTypes.all { buildType ->
//獲取自定義配置文件路徑
String obfuscatorConfigFile = getObfuscatorFilePath(project);
//把我們的配置加入到 Proguard文件列表中
buildType.proguardFile(obfuscatorConfigFile);
}
在合適的情況下生成proguard文件
新建一個Task,用來生成Proguard文件
variants.all { variant ->
//新建一個Task,用來生成Proguard文件
def Task processResourcesTask = variant.outputs[0].processResources
def addObfuscatorTask = project.task("addObfuscator${variant.buildType.name}") {
//生成Proguard文件
createObfuscatorConfigFile(project);
dependsOn processResourcesTask
}
//加入依賴,保證我們的task在javaCompile之前運行
javaCompile.dependsOn addObfuscatorTask
}
生成混淆文件
//生成混淆文件
private static String createObfuscatorConfigFile(Project project) {
final String obfuscator_path = getObfuscatorFilePath(project);
try {
File file = new File(obfuscator_path);
if (file.exists()) {
file.delete();
}
file.createNewFile();
file.write("""${getObfuscatorConfigure(project)}""", Charsets.UTF_8.toString());
} catch (e) {
return null;
}
return obfuscator_path;
}
獲取Proguard的具體配置內容
//獲取Proguard的具體配置內容
private final static String OBFUSCATOR_PKG_NAME = "com.helen.obfuscator";
private static String getObfuscatorConfigure(Project project) {
LogUtil.info(project, "get obfuscator configure");
String configs = "\n" +
"#[Start----]obfuscate.helper 自定義混淆聲明 \n" +
"\n" +
"-keep class " + OBFUSCATOR_PKG_NAME + ".* { *; }\n" +
"\n" +
"#類中所有成員不混淆(不包括內部類)\n" +
"#對應聲明 ObfuscateKeepAll\n" +
"-keep @" + OBFUSCATOR_PKG_NAME + ".ObfuscateKeepAll class * { *; }\n" +
"\n" +
"#類中所有public成員不混淆(不包括內部類)\n" +
"#對應聲明ObfuscateKeepPublic\n" +
"-keep @" + OBFUSCATOR_PKG_NAME + ".ObfuscateKeepPublic class * { \n" +
" public <fields>;\n" +
" public <methods>;\n" +
"}\n" +
"\n" +
"#保留帶注釋的成員,適用於類和內部類\n" +
"#對應聲明ObfuscateKeepThisField\n" +
"-keepclassmembers class * {\n" +
"@" + OBFUSCATOR_PKG_NAME + ".ObfuscateKeepThisField * ;\n" +
"}\n" +
"\n" +
"#保留類中set/get/is函數\n" +
"#對應聲明ObfuscateKeepSetterGetter\n" +
"-keepclassmembers @" + OBFUSCATOR_PKG_NAME + ".ObfuscateKeepSetterGetter class * {\n" +
" void set*(***);\n" +
" boolean is*(); \n" +
" *** get*();\n" +
"}\n" +
"#保留所有實現IObfuscateKeepAll接口的類,(注意接口有傳遞性,他的子類也會被keep)(內部類如果沒有繼承此接口會被混淆)\n" +
"#對應接口 IObfuscateKeepAll\n" +
"-keep class * implements " + OBFUSCATOR_PKG_NAME + ".IObfuscateKeepAll {\n" +
" *;\n" +
"}\n" +
" #[-----end]obfuscate.helper 自定義混淆聲明 ";
return configs;
};
至此, Obfuscator-plugin編寫完畢.
一. 引入插件
1. 在build.gradle配置依賴
buildscript {
repositories {
jcenter()
//1. 加入Jitpack倉庫
maven { url 'https://jitpack.io' }
}
dependencies {
...
//2. 加入obfuscator-plugin 插件
classpath 'com.github.helen-x:obfuscator-plugin:1.0'
}
}
2. build.gradle 使用plugin
//使用混淆插件
apply plugin: 'obfuscator-plugin'
引入完畢後,就可以直接使用啦.
二. 使用方法:
場景1:類中所有public成員不混淆(不包括內部類)
@ObfuscateKeepPublic
public class TestAnnotationKeepPublic{
}
場景2:類中所有成員不混淆(不包括內部類)(內部類如果不需要混淆也要加入此注釋)
@ObfuscateKeepAll
public class TestAnnotationKeepAll {
}
場景3:保留所有實現IObfuscateKeepAll接口的類,(注意接口有傳遞性,他的子類也會被keep)(內部類如果沒有繼承此接口會被混淆)
public class TestInterfaceKeep implements IObfuscateKeepAll{
}
場景4:保留帶注釋的成員,適用於類和內部類
public class TestAnnotationKeepThisField {
public String sName;
public static String sSName;
private int iValue;
@ObfuscateKeepThisField
private static int iSValue;
@ObfuscateKeepThisField
private void tFunc(){
}
}
場景5:保留類中set/get/is函數
@ObfuscateKeepSetterGetter
public class TestAnnotationKeepSetterGetter {}
最後更新:2017-10-25 10:34:26