Androidとセキュリティ:Android 2.3(Gingerbread) SDKに標準搭載されたProGuardを試す

はじめに

前回の記事ではAntを使う形で、難読化ツールであるProGuardの適用方法を紹介しました。*1
今回は本日(日本時間12/7)リリースされた Android 2.3(Gingerbread)のSDK(正確にはSDK Tools r8とADT8.0.0)に標準で搭載されたProGuardの適用方法について紹介します。

ProGuardとは

詳細は前回の記事を参照頂きたいのですが、ProGuardが初めての方の為に簡単に説明します。ProGuardはソースコードコンパイルする際に処理を最適化したり、プログラム中の変数やメソッドを意味のない文字列に置き換え、逆コンパイルされた際に処理の中身をわかりにくくする、いわゆる「難読化」を行うツールです。

これまでもAntを使ってProGuardの適用は行えましたが、最新のSDKでは標準搭載され、Eclipse上から簡単に難読化が行えるようになりました。

GoogleがProGuardを標準搭載した理由

他者による解析を難しくさせるツールを標準機能として載せるということは、裏を返せば「Androidのアプリケーションは、リバースエンジニアリングが容易」ということでしょう。Google公式サイトの解説ページに「ProGuardは完全にオプションとして動作するが、適用を"強くおすすめする"」と書かれています。「Androidアプリ開発者はコードレベルで知的財産を守るべき」という、Googleからの静かなるメッセージなのかもしれません。

ProGuardを有効にする方法

それではどのようにProGuardを適用すればよいか、順を追って解説します。

プロジェクトの作成

Android 2.3のSDKを利用してEclipse上でプロジェクトを新規作成すると、ProGuardの設定が記述された「proguard.cfg」ファイルが自動的に生成されます。このファイルには予め、標準的に必要となる設定が記載されていますので、基本的に修正の必要はありません。

default.propertiesの修正

作成したプロジェクトのホーム直下に配置されているdefault.propertiesの最終行に、下記を追記します。

proguard.config = <proguard.cfgの絶対パス> または <proguard.cfgのプロジェクトホームからの相対パス>

以下はプロジェクトのホームにproguard.cfgが配置されている状態での、default.propertiesの設定の例です。

# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# "build.properties", and override values to adapt the script to your
# project structure.

# Project target.
target=android-9
proguard.config=proguard.cfg

基本的にはこの設定だけでProGuardが有効になります!*2

ProGuardの設定変更

基本的に前節の設定のみでProGuardが実行されますが、下記のような場合は実行に失敗することがあります。

  • AndroidManifest.xmlファイルのみから参照されているクラス
  • JNIから呼び出されるメソッド
  • 動的参照される変数やメソッド

ProGuardの実行に失敗する場合は、proguard.cfgファイルに -keep オプションを付加した行を追記し、ProGuardの適用を除外する必要があります。

-keep public class <MyClass>

-keepオプションに関する詳しい情報は、前回の記事ProGuardのマニュアルを参照して下さい。

ProGuardを適用する

Antを使う

従来の方法と同様、ant release コマンドでProGuardを適用することが可能です。詳細は前回の記事を参照して下さい。

Eclipseのエクスポート機能を使う

メニューから、ファイル -> エクスポート -> Android -> Export Android application と選択して、アプリケーションへの署名を行い、apkファイルを出力します。
apkファイルの出力と同時に、下記のようなログがEclipseのコンソールビューに出力されているはずです。*3

[2010-12-07 13:05:28 - HelloAndroid] Refreshing resource folders.
[2010-12-07 13:05:28 - HelloAndroid] Starting incremental Pre Compiler: Checking resource changes.
[2010-12-07 13:05:28 - HelloAndroid] Nothing to pre compile!
[2010-12-07 13:05:28 - HelloAndroid] /android-sdk-mac_x86/platform-tools/aapt package -f -v -M /Users/seto/Documents/workspace/HelloAndroid/AndroidManifest.xml -S /Users/seto/Documents/workspace/HelloAndroid/res -A /Users/seto/Documents/workspace/HelloAndroid/assets -I /android-sdk-mac_x86/platforms/android-9/android.jar -F /var/folders/cb/cb2FqG62E5izgbafkF4ouE++--U/-Tmp-/android_1153826279934575801.ap_ 
[2010-12-07 13:05:28 - HelloAndroid] Found 0 custom asset files in /Users/seto/Documents/workspace/HelloAndroid/assets
[2010-12-07 13:05:28 - HelloAndroid] Locale/Vendor pairs:
[2010-12-07 13:05:28 - HelloAndroid]    /
[2010-12-07 13:05:28 - HelloAndroid]    /
[2010-12-07 13:05:28 - HelloAndroid]    /
[2010-12-07 13:05:28 - HelloAndroid]    /
[2010-12-07 13:05:28 - HelloAndroid] 
[2010-12-07 13:05:28 - HelloAndroid] ファイル:
[2010-12-07 13:05:28 - HelloAndroid]   drawable-hdpi/icon.png
[2010-12-07 13:05:28 - HelloAndroid]       Src: /Users/seto/Documents/workspace/HelloAndroid/res/drawable-hdpi/icon.png
[2010-12-07 13:05:28 - HelloAndroid]   drawable-ldpi/icon.png
[2010-12-07 13:05:28 - HelloAndroid]       Src: /Users/seto/Documents/workspace/HelloAndroid/res/drawable-ldpi/icon.png
[2010-12-07 13:05:28 - HelloAndroid]   drawable-mdpi/icon.png
[2010-12-07 13:05:28 - HelloAndroid]       Src: /Users/seto/Documents/workspace/HelloAndroid/res/drawable-mdpi/icon.png
[2010-12-07 13:05:28 - HelloAndroid]   layout/main.xml
[2010-12-07 13:05:28 - HelloAndroid]       Src: /Users/seto/Documents/workspace/HelloAndroid/res/layout/main.xml
[2010-12-07 13:05:28 - HelloAndroid]   values/strings.xml
[2010-12-07 13:05:28 - HelloAndroid]       Src: /Users/seto/Documents/workspace/HelloAndroid/res/values/strings.xml
[2010-12-07 13:05:28 - HelloAndroid]   AndroidManifest.xml
[2010-12-07 13:05:28 - HelloAndroid]       Src: /Users/seto/Documents/workspace/HelloAndroid/AndroidManifest.xml
[2010-12-07 13:05:28 - HelloAndroid] Including resources from package: /android-sdk-mac_x86/platforms/android-9/android.jar
[2010-12-07 13:05:28 - HelloAndroid] applyFileOverlay for drawable
[2010-12-07 13:05:28 - HelloAndroid] applyFileOverlay for layout
[2010-12-07 13:05:28 - HelloAndroid] applyFileOverlay for anim
[2010-12-07 13:05:28 - HelloAndroid] applyFileOverlay for xml
[2010-12-07 13:05:28 - HelloAndroid] applyFileOverlay for raw
[2010-12-07 13:05:28 - HelloAndroid] applyFileOverlay for color
[2010-12-07 13:05:28 - HelloAndroid] applyFileOverlay for menu
[2010-12-07 13:05:28 - HelloAndroid]     (processed image /Users/seto/Documents/workspace/HelloAndroid/res/drawable-hdpi/icon.png: 95% size of source)
[2010-12-07 13:05:28 - HelloAndroid]     (processed image /Users/seto/Documents/workspace/HelloAndroid/res/drawable-ldpi/icon.png: 89% size of source)
[2010-12-07 13:05:28 - HelloAndroid]     (processed image /Users/seto/Documents/workspace/HelloAndroid/res/drawable-mdpi/icon.png: 85% size of source)
[2010-12-07 13:05:28 - HelloAndroid]     (new resource id icon from drawable-hdpi/icon.png #generated)
[2010-12-07 13:05:28 - HelloAndroid]     (new resource id icon from drawable-hdpi/icon.png #generated)
[2010-12-07 13:05:28 - HelloAndroid]     (new resource id icon from drawable-hdpi/icon.png #generated)
[2010-12-07 13:05:28 - HelloAndroid]     (new resource id main from /Users/seto/Documents/workspace/HelloAndroid/res/layout/main.xml)
[2010-12-07 13:05:28 - HelloAndroid] Opening '/var/folders/cb/cb2FqG62E5izgbafkF4ouE++--U/-Tmp-/android_1153826279934575801.ap_'
[2010-12-07 13:05:28 - HelloAndroid] Writing all files...
[2010-12-07 13:05:28 - HelloAndroid]       'res/layout/main.xml' (compressed 49%)
[2010-12-07 13:05:28 - HelloAndroid]       'AndroidManifest.xml' (compressed 61%)
[2010-12-07 13:05:28 - HelloAndroid]       'resources.arsc' (not compressed)
[2010-12-07 13:05:28 - HelloAndroid]       'res/drawable-hdpi/icon.png' (not compressed)
[2010-12-07 13:05:28 - HelloAndroid]       'res/drawable-ldpi/icon.png' (not compressed)
[2010-12-07 13:05:28 - HelloAndroid]       'res/drawable-mdpi/icon.png' (not compressed)
[2010-12-07 13:05:28 - HelloAndroid] Generated 6 files
[2010-12-07 13:05:28 - HelloAndroid] Included 0 files from jar/zip files.
[2010-12-07 13:05:28 - HelloAndroid] Checking for deleted files
[2010-12-07 13:05:28 - HelloAndroid] 終了!
[2010-12-07 13:05:28 - HelloAndroid] ProGuard, version 4.4
[2010-12-07 13:05:28 - HelloAndroid] Reading input...
[2010-12-07 13:05:28 - HelloAndroid] Reading program jar [/private/var/folders/cb/cb2FqG62E5izgbafkF4ouE++--U/-Tmp-/android_16917046672536715.jar]
[2010-12-07 13:05:28 - HelloAndroid] Reading library jar [/android-sdk-mac_x86/platforms/android-9/android.jar]
[2010-12-07 13:05:30 - HelloAndroid] 初期化中...
[2010-12-07 13:05:30 - HelloAndroid] 注: the configuration refers to the unknown class 'com.android.vending.licensing.ILicensingService'
[2010-12-07 13:05:30 - HelloAndroid] 注: there were 1 references to unknown classes.
[2010-12-07 13:05:30 - HelloAndroid]       You should check your configuration for typos.
[2010-12-07 13:05:30 - HelloAndroid] Ignoring unused library classes...
[2010-12-07 13:05:30 - HelloAndroid]   Original number of library classes: 2750
[2010-12-07 13:05:30 - HelloAndroid]   Final number of library classes:    148
[2010-12-07 13:05:30 - HelloAndroid] Printing kept classes, fields, and methods...
[2010-12-07 13:05:30 - HelloAndroid] Shrinking...
[2010-12-07 13:05:30 - HelloAndroid] Printing usage to [/Users/seto/Documents/workspace/HelloAndroid/proguard/usage.txt]...
[2010-12-07 13:05:30 - HelloAndroid] Removing unused program classes and class elements...
[2010-12-07 13:05:30 - HelloAndroid]   Original number of program classes: 8
[2010-12-07 13:05:30 - HelloAndroid]   Final number of program classes:    2
[2010-12-07 13:05:30 - HelloAndroid] Optimizing...
[2010-12-07 13:05:30 - HelloAndroid]   Number of finalized classes:                 0
[2010-12-07 13:05:30 - HelloAndroid]   Number of vertically merged classes:         0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of horizontally merged classes:       0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed write-only fields:         0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of privatized fields:                 0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined constant fields:           0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of privatized methods:                0
[2010-12-07 13:05:30 - HelloAndroid]   Number of staticized methods:                2
[2010-12-07 13:05:30 - HelloAndroid]   Number of finalized methods:                 0
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed method parameters:         2
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined constant parameters:       1
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined constant return values:    0
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined short method calls:        0
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined unique method calls:       0
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined tail recursion calls:      0
[2010-12-07 13:05:30 - HelloAndroid]   Number of merged code blocks:                0
[2010-12-07 13:05:30 - HelloAndroid]   Number of variable peephole optimizations:   1
[2010-12-07 13:05:30 - HelloAndroid]   Number of arithmetic peephole optimizations: 0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of cast peephole optimizations:       0
[2010-12-07 13:05:30 - HelloAndroid]   Number of field peephole optimizations:      0
[2010-12-07 13:05:30 - HelloAndroid]   Number of branch peephole optimizations:     0
[2010-12-07 13:05:30 - HelloAndroid]   Number of simplified instructions:           0
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed instructions:              19
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed local variables:           6
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed exception blocks:          0
[2010-12-07 13:05:30 - HelloAndroid]   Number of optimized local variable frames:   1
[2010-12-07 13:05:30 - HelloAndroid] Shrinking...
[2010-12-07 13:05:30 - HelloAndroid] Removing unused program classes and class elements...
[2010-12-07 13:05:30 - HelloAndroid]   Original number of program classes: 2
[2010-12-07 13:05:30 - HelloAndroid]   Final number of program classes:    2
[2010-12-07 13:05:30 - HelloAndroid] Optimizing...
[2010-12-07 13:05:30 - HelloAndroid]   Number of finalized classes:                 0
[2010-12-07 13:05:30 - HelloAndroid]   Number of vertically merged classes:         0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of horizontally merged classes:       0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed write-only fields:         0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of privatized fields:                 0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined constant fields:           0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of privatized methods:                0
[2010-12-07 13:05:30 - HelloAndroid]   Number of staticized methods:                0
[2010-12-07 13:05:30 - HelloAndroid]   Number of finalized methods:                 0
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed method parameters:         0
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined constant parameters:       0
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined constant return values:    0
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined short method calls:        0
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined unique method calls:       0
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined tail recursion calls:      0
[2010-12-07 13:05:30 - HelloAndroid]   Number of merged code blocks:                0
[2010-12-07 13:05:30 - HelloAndroid]   Number of variable peephole optimizations:   0
[2010-12-07 13:05:30 - HelloAndroid]   Number of arithmetic peephole optimizations: 0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of cast peephole optimizations:       0
[2010-12-07 13:05:30 - HelloAndroid]   Number of field peephole optimizations:      0
[2010-12-07 13:05:30 - HelloAndroid]   Number of branch peephole optimizations:     0
[2010-12-07 13:05:30 - HelloAndroid]   Number of simplified instructions:           0
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed instructions:              2
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed local variables:           0
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed exception blocks:          0
[2010-12-07 13:05:30 - HelloAndroid]   Number of optimized local variable frames:   0
[2010-12-07 13:05:30 - HelloAndroid] Shrinking...
[2010-12-07 13:05:30 - HelloAndroid] Removing unused program classes and class elements...
[2010-12-07 13:05:30 - HelloAndroid]   Original number of program classes: 2
[2010-12-07 13:05:30 - HelloAndroid]   Final number of program classes:    2
[2010-12-07 13:05:30 - HelloAndroid] Optimizing...
[2010-12-07 13:05:30 - HelloAndroid]   Number of finalized classes:                 0
[2010-12-07 13:05:30 - HelloAndroid]   Number of vertically merged classes:         0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of horizontally merged classes:       0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed write-only fields:         0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of privatized fields:                 0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined constant fields:           0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of privatized methods:                0
[2010-12-07 13:05:30 - HelloAndroid]   Number of staticized methods:                0
[2010-12-07 13:05:30 - HelloAndroid]   Number of finalized methods:                 0
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed method parameters:         0
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined constant parameters:       0
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined constant return values:    0
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined short method calls:        0
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined unique method calls:       0
[2010-12-07 13:05:30 - HelloAndroid]   Number of inlined tail recursion calls:      0
[2010-12-07 13:05:30 - HelloAndroid]   Number of merged code blocks:                0
[2010-12-07 13:05:30 - HelloAndroid]   Number of variable peephole optimizations:   0
[2010-12-07 13:05:30 - HelloAndroid]   Number of arithmetic peephole optimizations: 0   (使用不可)
[2010-12-07 13:05:30 - HelloAndroid]   Number of cast peephole optimizations:       0
[2010-12-07 13:05:30 - HelloAndroid]   Number of field peephole optimizations:      0
[2010-12-07 13:05:30 - HelloAndroid]   Number of branch peephole optimizations:     0
[2010-12-07 13:05:30 - HelloAndroid]   Number of simplified instructions:           0
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed instructions:              0
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed local variables:           0
[2010-12-07 13:05:30 - HelloAndroid]   Number of removed exception blocks:          0
[2010-12-07 13:05:30 - HelloAndroid]   Number of optimized local variable frames:   0
[2010-12-07 13:05:30 - HelloAndroid] Obfuscating...
[2010-12-07 13:05:30 - HelloAndroid] Printing mapping to [/Users/seto/Documents/workspace/HelloAndroid/proguard/mapping.txt]...
[2010-12-07 13:05:30 - HelloAndroid] Writing output...
[2010-12-07 13:05:30 - HelloAndroid] Preparing output jar [/private/var/folders/cb/cb2FqG62E5izgbafkF4ouE++--U/-Tmp-/android_2607110758288445210.jar]
[2010-12-07 13:05:30 - HelloAndroid]   Copying resources from program jar [/private/var/folders/cb/cb2FqG62E5izgbafkF4ouE++--U/-Tmp-/android_16917046672536715.jar]
[2010-12-07 13:05:30 - HelloAndroid] Printing classes to [/Users/seto/Documents/workspace/HelloAndroid/proguard/dump.txt]...

下記のように出力されていれば、難読化がされています。

[2010-12-07 13:05:30 - HelloAndroid] Obfuscating...
[2010-12-07 13:05:30 - HelloAndroid] Printing mapping to [/Users/seto/Documents/workspace/HelloAndroid/proguard/mapping.txt]...

ProGuard有効時に出力されるファイル/ディレクト

ProGuardが適用されると、<プロジェクトのホーム>/proguard/ にディレクトリが生成され、その中に下記のようなファイルが出力されます。*4

  • dump.txt:.apkファイル中のクラスの内部構造が記載されています。
  • mapping.txt:難読化前と難読化後のクラス、メソッド、変数の対応リスト
  • seed.txt:難読化されていないクラスやメンバのリスト
  • usage.txt:.apkファイルから外されたコードのリスト

特に、mapping.txtは、リリースビルドしたapkから送られる難読化されたバグリポート(スタックトレース)を可読化するために必要となります。

ProGuard利用時の注意点

proguardディレクトリは、リリースビルドの際に上書きされます。これらを失うと、リリース後のバグレポートから原因を調査することが困難になりますので、proguardディレクトリの中身(特にmapping.txtファイル)は、リリースバージョン毎に保存しておく必要があります。
Android開発者サイトでは、保存の方法として下記のようなものが推奨されています。

  • リリース時のバージョンまたはビルド番号を、フォルダやファイル名に追記する
  • バージョン管理システムにファイルを登録する

難読化されたスタックトレースをデコードする方法

ProGuardは難読化において相応の効果を発揮するツールではありますが、反面、デバッグに必要な情報までをも難読化させてしまう欠点があります。そのため、最新のAndroid SDKには、難読化されたスタックトレースを可読化するための、シェルスクリプトが付属されています。/tools/proguard/ ディレクトリにretrace.sh(Windowsの場合、retrace.bat)が格納されていますので、このスクリプトを使用します。

コマンドの書式:

retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]

使用例:

retrace.sh -verbose mapping.txt obfuscated_trace.txt

例のmapping.txtは、リリース時に保存しておいたファイル、obfuscated_trace.txtは、難読化されたバグリポート中のスタックトレースを記載したファイルです。
ちなみに、スタックトレースを記載したファイルを省略した場合、標準入力からスタックトレースを読み込んで、デコードします。


より簡単に使えるようになったProGuardで、あなたのアプリケーションを守りましょう!

文責:技術部 植物工場研究G 瀬戸 直喜

*1:書いた矢先に標準搭載され、記事の価値が半分になり、ついカッとなって追加記事を書いたりはしていません。

*2:簡単すぎて前回の記事は一体何だったんだと言いたくなります。

*3:ログが出力されない場合は、Eclipseのメニュー->環境設定->Android->ビルドの「ビルド出力」のオプションを「詳細」に設定してみてください。

*4:Antで実行した場合、出力先は<プロジェクトのホーム>/bin/proguard/ となります。