閱讀115 返回首頁    go 技術社區[雲棲]


混淆小助手-Obfuscator

涉及的技術:

1.自定義Gradle plugin
2.Jitpack發布開源庫
3.Proguard代碼混淆
4.Android Gradle編譯

Github地址:

Obfucator源碼
obfuscator-plugin源碼

一.為什麼要做混淆

Android是java開發的, 所以很容易被反編譯.為了提升app的安全等級, 需要對app進行混淆. 常用有3種處理方式:

  1. proguard代碼混淆
  2. dexguard
  3. apk 加殼

現在市麵上有很多加殼平台,可以防止app被反編譯.
但是這不是萬能的,因為脫殼技術也日益更新,如果app脫殼成功,代碼就可以全被看到了,所以很有必要對代碼進行proguard混淆. 這可以
1).可以減少apk的大小;
2)加大反編譯後讀取代碼邏輯的難度.

二.不能混淆的幾種case

app中有些代碼是不能混淆的,所以我們要編寫keep配置,讓他保持不混淆
例如,保持com.xiaomi包下的類不被混淆

-keep class com.xiaomi.**{*;}

常見不能混淆的2種case:

  1. 反射調用到的類或者變量
  2. 用於解釋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個痛點:

  1. 配置粒度較細的混淆很困難
  2. 因為寫混淆配置麻煩, 所以混淆一般都是寫完一部分代碼後, 再寫混淆配置. 這造成寫配置的時候都不記得哪些需要加上混淆配置了.

一.混淆助手Obfuscator

Obfucator源碼

混淆小助手Obfuscator可以:

  1. 控製粒度很細的混淆
  2. 寫代碼的時候,可以很方便的隨手寫上混淆.

實現原理:

實現思路是利用"注解/接口"控製混淆, 注解的優勢是寫代碼可以隨手寫上; 接口的優勢是類可以實現多個接口.

場景: 保持某個 "方法/變量" 不被混淆

回到上麵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進行處理了.

按照這個套路豐富了一下注解聲明,就有了混淆小助手
screenshot.png

Obfuscator現在支持:

  1. 類中所有public成員不混淆(不包括內部類)
  2. 類中所有成員不混淆(不包括內部類)(內部類如果不需要混淆也要加入此注釋)
  3. 保留所有實現IObfuscateKeepAll接口的類,(注意接口有傳遞性,他的子類也會被keep)(內部類如果沒有繼承此接口會被混淆)
  4. 保留帶注釋的成員,適用於類和內部類
  5. 保留類中set/get/is函數

二.發布到Jitpack

把前麵聲明的注解發布到Jitpack上, 這樣所有項目都可以複用. 過程很簡單,幾分鍾完事.
Jitpack發布開源庫

三.編寫 Gradle plugin

obfuscator-plugin源碼

上麵的混淆需要手動把混淆配置拷貝到 proguard-rules.pro, 這個手動的步驟也略麻煩. Gradle plugin功能這麼強大, 我們來利用 編譯插件來自動完成 混淆配置.

自定義Plugin方法見文章自定義Gradle plugin

對於obfuscator-plugin,需要解決3個問題:

  1. 在"Android App&libaray"中才生效
  2. 把Jitpack中obfuscator的依賴自動加進來
  3. 生成混淆配置,並且讓其生效

他的結構如下
screenshot.png

下麵分析一下實現:

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;
    }
}

那我們需要做的是:

  1. 在buildType中加入Proguard
  2. 在合適的情況下生成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

  上一篇:go  學習ASP.NET Core,怎能不了解請求處理管道[1]: 中間件究竟是個什麼東西?
  下一篇:go  學習ASP.NET Core, 怎能不了解請求處理管道[4]: 應用的入口——Startup