美團Android資源混淆保護實踐

jopen 9年前發布 | 15K 次閱讀 Android Android開發 移動開發

 

前言

Android應用中的APK安全性一直遭人詬病,市面上充斥著各種被破解或者漢化的應用,破解者可以非常簡單的通過破解工具就能對一個APK進 行反編譯、破解、漢化等等,這樣就可以修改原有代碼的邏輯、添加新代碼、添加或修改資源、或者更有甚者植入病毒等等,從而破壞原有APK的安全和用戶體 驗,最終傷害到用戶和原有的開發者。而事物都是有兩方面的,有矛就有盾,針對Android應用安全的各種方案應運而生,大家比較熟悉一般是各類加殼加固 的工具,我們可以使用這些工具來保護我們的APK,加殼加固是另外一個話題了,我們這里不對加殼加固進行介紹,后續如果有機會會單獨開一個話題討論,我們 在開發過程中可以通過ProGuard或者DexGuard來保護我們的代碼,從而實現相對的代碼安全,但我們的資源呢?我們往往忽略對資源文件的保護, 那這里將要分享的是如果采用常規方式對APK中的資源文件進行保護。

資源安全

資源安全這個話題目前大家關注度不算太高,相比較而言大家更關注代碼安全,目前市面上各類APP基本都使用了ProGuard來保護代碼的安全, 但對資源文件的保護力度都不大,其實資源文件是存在比較大的安全隱患,那資源會有哪些安全隱患呢?下面我們通過一個比較簡單的例子來說明下保護資源文件的 重要性。

我們先用最常見的apktool工具來反編譯一個應用來看看,通過運行下面命令就能進行反編譯;

apktool d -s xxx.apk

反編譯成功后我們來看下反編譯得到的文件結構(見下圖);

美團Android資源混淆保護實踐

通過上圖中的目錄結構,我們可以看到這個應用的資源文件大概有:anim、drawable、layout、menu、values等等,我們 可以通過修改這些文件夾下的資源文件,并通過apktool進行回編譯(apktool b 命令)就能創建一個經過修改過的APK應用,例如我們修改下圖中紅色橫線所標示的layout文件,就能往原有APK的支付信息(根據資源名稱猜測這個 layout的意圖)中添加一些我們自己的東西;

美團Android資源混淆保護實踐

這個問題主要是因為我們在開發過程中倡導命名的規范性,一般都要求在命名時做到見名知意,這樣能夠方便我們自己的理解和維護,但同時這也方便了破解者,破解者可以輕松的根據文件名稱來猜測這個文件的意圖和作用,從而做破壞性的修改。

通過這個例子我們可以看出目前資源安全的重要性,那如何做到資源安全呢?安全都是相對的,沒有絕對的安全,我們接下來要討論的是類似 Proguard方式的對我們的資源進行保護。我們主要是通過修改AAPT工具來對資源進行保護,為了方便理解,下面先講一下Android應用是怎么查 找資源的。

Android查找資源的流程

在Android系統中,每一個應用程序一般都會配置很多資源,用來適配不同密度、大小和方向的屏幕,以及適配不同的國家、地區和語言等等。這 些資源是在應用程序運行時自動根據設備的當前配置信息進行適配的。這也就是說,給定一個相同的資源ID,在不同的設備配置之下,查找到的可能是不同的資 源。

這個查找過程對應用程序來說,是完全透明的,這個過程主要是靠Android資源管理框架來完成的,而Android資源管理框架實際是由 AssetManager和Resources兩個類來實現的。其中,Resources類可以根據ID來查找資源,而AssetManager類根據文 件名來查找資源。事實上,如果一個資源ID對應的是一個文件,那么Resources類是先根據ID來找到資源文件名稱,然后再將該文件名稱交給 AssetManager類來打開對應的文件的。基本流程如下圖:

美團Android資源混淆保護實踐

通過上圖我們可以看到Resources是通過resources.arsc把Resource的ID轉化成資源文件的名稱,然后交由 AssetManager來加載的。而Resources.arsc這個文件是存放在APK包中的,他是由AAPT工具在打包過程中生成的,他本身是一個 資源的索引表,里面維護者資源ID、Name、Path或者Value的對應關系,AssetManager通過這個索引表,就可以通過資源的ID找到這 個資源對應的文件或者數據。

AAPT介紹

AAPT是Android Asset Packaging Tool的縮寫,它存放在SDK的tools/目錄下,AAPT的功能很強大,可以通過它查看查看、創建、更新壓縮文件(如 .zip文件,.jar文件, .apk文件), 它也可以把資源編譯為二進制文件,并生成resources.arsc, AAPT這個工具在APK打包過程中起到了非常重要作用,在打包過程中使用AAPT對APK中用到的資源進行打包,這里不對AAPT這個工具做過多的討 論,只看一下AAPT這個工具在打包過程中起到的作用,下圖是AAPT打包的流程:

美團Android資源混淆保護實踐

AAPT這個工具在打包過程中主要做了下列工作:

  1. 把"assets"和"res/raw"目錄下的所有資源進行打包(會根據不同的文件后綴選擇壓縮或不壓縮),而"res/"目錄下的其他資 源進行編譯或者其他處理(具體處理方式視文件后綴不同而不同,例如:".xml"會編譯成二進制文件,".png"文件會進行優化等等)后才進行打包;
  2. 會對除了assets資源之外所有的資源賦予一個資源ID常量,并且會生成一個資源索引表resources.arsc;
  3. 編譯AndroidManifest.xml成二進制的XML文件;
  4. 把上面3個步驟中生成結果保存在一個*.ap_文件,并把各個資源ID常量定義在一個R.java中;
.ap_這個文件會在生成APK時放入APK包中, .ap

這個文件本身是一個ZIP包,他里面包含resources.arsc、AndroidManifest.xml、assets以及所有的資源文件,下圖是UNZIP后的截圖:

美團Android資源混淆保護實踐
這個文件中包含的內容,這個文件存放在build/intermediates/res的目錄下,下圖是這個文件存放的路徑截圖:
美團Android資源混淆保護實踐

資源保護

我們這里參考Proguard Obfuscator方式,對APK中資源文件名使用簡短無意義名稱進行替換,給破解者制造困難,從而做到資源的相對安全;通過上面分析,我們可以看出通 過修改AAPT在生成resources.arsc和*.ap_時把資源文件的名稱進行替換,從而保護資源。通過閱讀AAPT編譯資源的代碼,我們發現修 改AAPT在處理資源文件相關的源碼是能夠做到資源文件名的替換,下面是Resource.cpp中makeFileResources()的修改的代碼 片段:

  static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets,
                  ResourceTable* table,
                  const sp<ResourceTypeSet>& set,
                  const char* resType)
{
  String8 type8(resType);
  String16 type16(resType);
  bool hasErrors = false;
  ResourceDirIterator it(set, String8(resType));
  ssize_t res;
  while ((res=it.next()) == NO_ERROR) {
    if (bundle->getVerbose()) {
      printf("  (new resource id %s from %s)\n",
           it.getBaseName().string(), it.getFile()->getPrintableSource().string());
    }
    String16 baseName(it.getBaseName());
    const char16_t* str = baseName.string();
    const char16_t* const end = str + baseName.size();
    while (str < end) {
      if (!((*str >= 'a' && *str <= 'z')
          || (*str >= '0' && *str <= '9')
          || *str == '_' || *str == '.')) {
        fprintf(stderr, "%s: Invalid file name: must contain only [a-z0-9_.]\n",
            it.getPath().string());
        hasErrors = true;
      }
      str++;
    }
    String8 resPath = it.getPath();
    resPath.convertToResPath();
    String8 obfuscationName;
    String8 obfuscationPath = getObfuscationName(resPath, obfuscationName);
    table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()),
            type16,
            baseName, // String16(obfuscationName),
            String16(obfuscationPath), // resPath
            NULL,
            ?.getParams());
    assets->addResource(it.getLeafName(), obfuscationPath/*resPath*/, it.getFile(), type8);
  }
  return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
}

上述代碼是在ResourceTable和Assets中添加資源文件時, 對資源文件名稱進行修改,這就能夠做到資源文件名稱的替換,這樣通過使用修改過的AAPT編譯資源并進行打包,我們再用上面講到的apktool這個工具進行反編譯,下圖是反編譯后的截圖:

美團Android資源混淆保護實踐

發現什么變化了嗎?在res目錄下熟悉的layout、drawable、anim、menu等文件夾不見了,那他們去哪了呢?因為apktool工具把它們放到了unknown文件夾下了,見下圖:

美團Android資源混淆保護實踐

讓我們來看一下unknown文件夾,你會發現資源文件名已經被簡短無意義名稱進行替換了,這樣會給反編譯者制造理解上的困難,反編譯者需要消 耗一定的時間來搞清楚這些資源文件的作用,資源混淆帶來的另外一個好處是能明顯減小APK的大小,資源混淆既能保護資源文件的安全又能減小安裝包的大小, 那我們何樂而不為呢?

這樣通過修改AAPT,我們可以在代碼零修改的基礎下就能做到相對的資源安全,當然安全是相對的,沒有絕對的安全。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!