2015年7月10日金曜日
2012年3月26日月曜日
はじめに
前回の記事では、ADT8で標準搭載されたProGuardの適用方法について解説しました。今回、ADT17でProGuardのいくつかの改善が行われたため、これら改善点について解説します。尚、本記事はProGuard Improvementsを元に翻訳したものです。
Androidに統合されたProGuardに関する改善点(ADT17)
はじめに
前回の記事では、ADT8で標準搭載されたProGuardの適用方法について解説しました。今回、ADT17でProGuardのいくつかの改善が行われたため、これら改善点について解説します。尚、本記事はProGuard Improvementsを元に翻訳したものです。
2010年12月8日水曜日
はじめに
前回の記事では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には、難読化されたスタックトレースを可読化するための、シェルスクリプトが付属されています。<sdk_root>/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/ となります。
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には、難読化されたスタックトレースを可読化するための、シェルスクリプトが付属されています。<sdk_root>/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/ となります。
2010年11月29日月曜日
はじめに
このエントリでは、Androidアプリという知的財産を守る方法の一つとして、難読化ツールであるProGuardを紹介し、実際にAndroidアプリに組み込む方法を示します。
ソフトウェアの価値
ソフトウェアでは処理の流れ(アルゴリズム)を記述したソースコードが大きな価値を持つため、(特に商用のソフトウェアは)ソースコードを非公開とし、コンピュータで実行可能な必要最小限の中間コードやネイティブコードに限って配布することで、ソフトウェアビジネスを成立させています。つまり、処理の流れを「隠す」ことにより、その会社(あるいは人)にしか解決できないという「情報に対する付加価値」を与え、ソフトウェアビジネスを成立させていると言えます。一方で、何らかの形でこの「隠した部分」が明らかになり白日の下に晒されると、類似したソフトウェアが流通するなど、もはや処理の流れという情報としての「価値」は失われ、ビジネスとして成立しなくなります。
さて、Androidに限らず、最近のソフトウェアは高級言語(Javaなど)を用いて記述され、翻訳(コンパイル)を通じて最終的にはコンピュータが処理可能な「中間コード」や「ネイティブコード」に変換されます。この変換は言語ごとに定められた「文法」によって翻訳されるため、その翻訳ルールさえ判れば逆翻訳(デコンパイルやデアセンブルと言います)が可能です。つまり、「中間コード」や「ネイティブコード」から逆翻訳を行い、人間が理解可能なソースコードを生成することが可能ということです。
このままでは、ちょっとした知識があればソースコードが推測されてしまいます。その為、商用ソフトなどでは難読化を行い、逆翻訳された場合でもソースコードを読みにくくするなどして、簡単に処理の流れが明らかにならないように対策しています。
難読化とは
先に記した通り、難読化とは「ソースコードを読みにくくすること」です。難読化の手法は様々ありますが、下記のような方法によって、翻訳時にソースコードを可能な限り読みにくくします。
- クラスやメソッド、変数名を省略・無意味化
- コメントの削除
- 空白(行)の削除
また、翻訳時に処理パフォーマンスが良くなるように処理の最適化(繰り返し処理の簡略化、関数・メソッドのインライン化等)が行われますが、その結果、人間にとって理解が困難なコードとなることもあります。
Androidの難読化ツール
Androidのビルドプロセスにも適用可能な難読化ツールが存在します。その名も「ProGuard」。
ProGuardは、Javaのクラスファイルの圧縮・難読化・最適化・前検証を行うツールです。使われていないクラス/メソッド/変数を検知・削除したり、バイトコードを最適化したり、クラスやメソッド名を無意味化したりします。ProGuardは一般的なAndroidアプリケーション開発に用いられるEclipseではなく、CUIベースのビルドツール「Ant」に統合される形で利用できます。Antによって、Androidアプリケーションの翻訳や難読化はもちろん、アプリケーションの署名や端末へのインストールも自動化することができます。
ProGuardの概要
引用:http://proguard.sourceforge.net/manual/introduction.html
ProGuardは、jar/war/zip/ディレクトリなどを入力として、次の4つのステップを経て、最終的にjar/war/zip/ディレクトリへ処理結果を出力します。
- 圧縮ステップ(shrink)
- 最適化ステップ(optimize)
- 難読化ステップ(obfuscate)
- 前検証ステップ(preverify)
圧縮ステップでは、プログラム中の利用されていないクラスやメソッド等を取り除きます。このステップは再帰的に処理されるので、全てのクラス・メソッドが対象となります。
最適化ステップでは、エントリポイントではないクラスやメソッドをprivate/static/final属性に変換、使われていないパラメータは削除され、一部のメソッドはインライン化されます。
難読化ステップでは、エントリポイントではないクラスやメソッドの名前を変更します。エントリポイントとなるクラスやメソッドは、オリジナルの名前でアクセスできるようにするために変更されません。
前検証ステップでは、実行時・ロード時のバイトコードの検証負荷を減らすために、あらかじめ型情報を調査し、その情報をクラスファイル内に添付します。Android(Java6)では使用しません。
開発したプログラムは外部から提供されるライブラリを使用することがありますが、これらはProGuardの処理の対象外となります。
ProGuardの適用
それでは、以下ではAndroidのプロジェクトにProGuardを適用し、難読化&最適化を行う方法について説明しましょう。
システム要件
AndroidのプロジェクトにProGuardを適用する場合、次の条件を満たしている必要があります:
- Android SDKがインストールされていること
- Android SDK Toolsがインストールされていて、少なくともリビジョン7以上であること
- Apache Ant 1.6.5以上(Linux and Mac)/1.7以上(Windows) :Eclipse付属のものを使わず、最新のAntを別途インストールすることが望ましい。
Android SDK Tools Rev.7には、プリコンパイルまたはコンパイル時のユーザー定義処理をサポートするフックを含む、Antビルドルールファイルが同梱されています。
サンプルプロジェクト
今回のサンプルは下記のような比較的シンプルな処理を含みます。ソースコード中には無意味な処理が含まれますが、ProGuardの適用状態が判るようにするためのものです。ここでは処理の内容に注目する必要はありません。
プロジェクトのホームを /HelloAndroidとします。このホームは各自の環境に合わせて読み替えてください。
HelloAndroid/src/test/hello/HelloAndroid.java
package test.hello;
import android.app.Activity;
import android.os.Bundle;
public class HelloAndroid extends Activity
{
public static final class testClass {
testClass() {
methodA(1,"str");
}
private void methodA(int i, String j) {
return;
}
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
testMethod1();
new testClass();
}
private void testMethod1()
{
int i,sum;
for( i = 0, sum = 0; i < 10; i++ ) {
sum += i;
}
}
}
リソースファイル等はデフォルトのものをそのまま使用します。
署名キーの用意
ProGuardには直接的に関係無いが、今回はEclipseを使用してビルド・デプロイしないため、ビルド後に自動でデプロイするために予め署名キーを生成しておくと、後が便利です。既に署名キーを用意している場合は、読み飛ばしてもらって構いません。
$ keytool -genkey -v -keystore release.keystore -alias testhello -keyalg RSA -keysize 4096 -validity 10000
キーストアのパスワードを入力してください: <パスワードを入力>
新規パスワードを再入力してください: <もう一度パスワードを入力>
姓名を入力してください。
[Unknown]: Seto Naoki
組織単位名を入力してください。
[Unknown]:
組織名を入力してください。
[Unknown]: Brilliant Service co.,Ltd.
都市名または地域名を入力してください。
[Unknown]: Tokyo
州名または地方名を入力してください。
[Unknown]:
この単位に該当する 2 文字の国番号を入力してください。
[Unknown]: jp
CN=Seto Naoki, OU=Unknown, O=Brilliant Service co.,Ltd., L=Tokyo, ST=Unknown, C=jp でよろしいですか?
[no]: yes
10,000 日間有効な 4,096 ビットの RSA の鍵ペアと自己署名型証明書 (SHA1withRSA) を生成しています
ディレクトリ名: CN=Seto Naoki, OU=Unknown, O="Brilliant Service co.,Ltd.", L=Tokyo, ST=Unknown, C=jp
<testhello> の鍵パスワードを入力してください。
(キーストアのパスワードと同じ場合は RETURN を押してください): <リターンキーを押す>
[release.keystore を格納中]
プロジェクトの更新
下記のコマンドでAntビルドできるようにプロジェクトを更新します。
cd /HelloAndroid/.. (プロジェクトホームの一つ上)
android update project --path ./HelloAndroid
更新すると下記のファイルが作成されているはずです。
- default.properties
- local.properties
- build.properties
- build.xml
ProGuard用設定ファイルの配置
下記のファイルをプロジェクトのホームに配置します。これらのファイルはオフィシャルブログからもダウンロードできます。
- add-proguard-release.xml
- procfg.txt
/HelloAndroid/add-progurad-release.xml
<property name="obfuscate.dir" value="obf" />
<property name="obfuscate.absolute.dir" location="${obfuscate.dir}" />
<property name="android-jar-preobfuscate" value="${obfuscate.absolute.dir}/original.jar" />
<property name="android-jar-postobfuscate" value="${obfuscate.absolute.dir}/postobf.jar" />
<property name="out.dex.input.absolute.dir" value="${android-jar-postobfuscate}" />
<target name="-post-compile" depends="-dex-obfuscate,-dex-no-obfuscate">
</target>
<target name="-dex-no-obfuscate" unless="build.mode.release">
<mkdir dir="${obfuscate.absolute.dir}" />
<jar basedir="${out.classes.dir}" destfile="${android-jar-postobfuscate}" />
</target>
<target name="-dex-obfuscate" if="build.mode.release">
<property name="proguard-jar" value="${proguard.dir}/proguard.jar" />
<property name="proguard-conf.dir" value="" />
<property name="proguard-conf.absolute.dir" location="${proguard-conf.dir}" />
<property name="proguard-conf" value="${proguard-conf.absolute.dir}/procfg.txt" />
<path id="fullclasspath">
<path refid="android.target.classpath"/>
<pathelement path="${external.libs.dir}"/>
</path>
<property name="libraryjarpath" refid="fullclasspath"/>
<taskdef resource="proguard/ant/task.properties" classpath="${proguard-jar}" />
<mkdir dir="${obfuscate.absolute.dir}" />
<delete file="${android-jar-preobfuscate}"/>
<delete file="${android-jar-postobfuscate}"/>
<jar basedir="${out.classes.dir}" destfile="${android-jar-preobfuscate}" />
<proguard>
@${proguard-conf}
-injars ${android-jar-preobfuscate}
-outjars ${android-jar-postobfuscate}
-libraryjars ${libraryjarpath}
-dump ${obfuscate.absolute.dir}/dump.txt
-printseeds ${obfuscate.absolute.dir}/seeds.txt
-printusage ${obfuscate.absolute.dir}/usage.txt
-printmapping ${obfuscate.absolute.dir}/mapping.txt
</proguard>
</target>
このファイルでは、AntでProGuardを使用する際のパラメータ(出力先ディレクトリなど)の定義、ant_rules_r3で定義されるAntのプリコンパイルルールの書き換え、Ant用ProGuardタスクの追加の定義などを行います。
/HelloAndroid/procfg.txt
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class com.android.vending.licensing.ILicensingService
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
ここでは最適化の設定や難読化をしない設定(-keep*)を行っています。ビルドエラーが発生する場合、設定を変更する必要があります。この辺りのテクニックは以下のサイトが参考になります。
http://d.hatena.ne.jp/hyoromo/20101120/1290216449
ビルド設定ファイルの修正
build.propertiesとbuild.xmlを改修してProGuardに対応させます。
/HelloAndroid/build.properties
標準の設定ファイルに、下記の設定を追加します。
- 自動デプロイのオプション設定(12~16行目)
- 自動署名の設定(18~26行目)
- ProGuardの有効化(33行目)
01 : # This file is used to override default values used by the Ant build system.
02 : #
03 : # This file must be checked in Version Control Systems, as it is
04 : # integral to the build system of your project.
05 :
06 : # This file is only used by the Ant script.
07 :
08 : # You can use this to override default values such as
09 : # 'source.dir' for the location of your java source folder and
10 : # 'out.dir' for the location of your output folder.
11 :
12 : #-d:device/-e:emulator
13 : adb.device.arg=
14 :
15 : #adb install <option>
16 : adb.install.option=-r
17 :
18 : # You can also use it define how the release builds are signed by declaring
19 : # the following properties:
20 : # 'key.store' for the location of your keystore and
21 : # 'key.alias' for the name of the key to use.
22 : # The password will be asked during the build when you use the 'release' target.
23 : key.store=<キーストアのファイルパス(ファイル名含む)>
24 : key.store.password=<キーストアのパスワード>
25 : key.alias=<キー生成時に指定したエイリアス>
26 : key.alias.password=<エイリアスのパスワード>
27 :
28 : # The name of your application package as defined in the manifest.
29 : # Used by the 'uninstall' rule.
30 : application.package=test.hello
31 :
32 : # For ProGuard
33 : proguard.dir=<ProGuardのlibディレクトリまでのパス>
proguard.dirは<ProGuardのインストール先>\libを指定する必要があります。
/HelloAndroid/build.xml
標準の設定ファイルに、下記の設定を追加します。
- ProGuard用設定XMLファイルの読み込み(2~6行目)
- アプリケーションへの自動署名とデプロイタスクの定義(88~103行目)
01 : <?xml version="1.0" encoding="UTF-8"?>
02 : <!DOCTYPE project [
03 : <!ENTITY add-proguard-release SYSTEM "add-proguard-release.xml">
04 : ]>
05 : <project name="HelloAndroid" default="help">
06 : &add-proguard-release;
07 :
08 :
12 : <property file="local.properties" />
13 :
14 :
31 : <property file="build.properties" />
32 :
33 :
37 : <property file="default.properties" />
38 :
39 :
42 : <path id="android.antlibs">
43 : <pathelement path="${sdk.dir}/tools/lib/anttasks.jar" />
44 : <pathelement path="${sdk.dir}/tools/lib/sdklib.jar" />
45 : <pathelement path="${sdk.dir}/tools/lib/androidprefs.jar" />
46 : </path>
47 :
48 : <taskdef name="setup"
49 : classname="com.android.ant.SetupTask"
50 : classpathref="android.antlibs" />
51 :
52 :
54 :
66 :
67 :
68 :
86 : <setup />
87 :
88 :
89 : <macrodef name="install-release-helper">
90 : <sequential>
91 : <echo>Installing ${out.release.file}...</echo>
92 : <exec executable="${adb}" failonerror="true">
93 : <arg line="${adb.device.arg}" />
94 : <arg value="install" />
95 : <arg value="${adb.install.option}" />
96 : <arg path="${out.release.file}" />
97 : </exec>
98 : </sequential>
99 : </macrodef>
101:
101: <target name="deploy" depends="release">
102: <install-release-helper />
103: </target>
104:
105:</project>
ProGuardの適用(ビルド)とデプロイ
ビルドとデプロイは簡単です。アプリを配置したいデバイスを接続し(または、abdコマンドでシミュレータ上にデバイスを作成しておいて)、下記のコマンドを実行します。
$ ant deploy
これまでに示した定義ファイルの記述が正しければ、ProGuardが適用された状態でビルドされ、アプリへの自動署名が行われて端末にアプリがインストールされます。
Buildfile: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\build.xml
[setup] Android SDK Tools Revision 7
[setup] Project Target: Google APIs
[setup] Vendor: Google Inc.
[setup] Platform Version: 1.5
[setup] API level: 3
[setup]
[setup] ------------------
[setup] Resolving library dependencies:
[setup] ------------------
[setup] Ordered libraries:
[setup] ------------------
[setup]
[setup] WARNING: No minSdkVersion value set. Application will install on all Android versions.
[setup]
[setup] Importing rules file: tools\ant\ant_rules_r3.xml
-set-release-mode:
-dirs:
[echo] Creating output directories if needed...
[mkdir] Created dir: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin
[mkdir] Created dir: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\gen
[mkdir] Created dir: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin\classes
-pre-build:
-resource-src:
[echo] Generating R.java / Manifest.java from the resources...
-aidl:
[echo] Compiling aidl files into Java classes...
-pre-compile:
compile:
[javac] C:\android-sdk-windows\tools\ant\ant_rules_r3.xml:336: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
[javac] Compiling 2 source files to C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin\classes
-dex-obfuscate:
[mkdir] Created dir: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf
[jar] Building jar: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf\original.jar
[proguard] ProGuard, version 4.5.1
[proguard] Reading input...
[proguard] Reading program jar [C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf\original.jar]
[proguard] Reading library jar [C:\android-sdk-windows\platforms\android-3\android.jar]
[proguard] Reading library jar [C:\android-sdk-windows\add-ons\addon_google_apis_google_inc_3\libs\maps.jar]
[proguard] Reading library directory [C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\libs]
[proguard] Initializing...
[proguard] Note: the configuration refers to the unknown class 'com.android.vending.licensing.ILicensingService'
[proguard] Note: there were 1 references to unknown classes.
[proguard] You should check your configuration for typos.
[proguard] Ignoring unused library classes...
[proguard] Original number of library classes: 2398
[proguard] Final number of library classes: 92
[proguard] Printing kept classes, fields, and methods...
[proguard] Shrinking...
[proguard] Printing usage to [C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf\usage.txt]...
[proguard] Removing unused program classes and class elements...
[proguard] Original number of program classes: 6
[proguard] Final number of program classes: 2
[proguard] Optimizing...
[proguard] Number of finalized classes: 0
[proguard] Number of vertically merged classes: 0 (disabled)
[proguard] Number of horizontally merged classes: 0 (disabled)
[proguard] Number of removed write-only fields: 0 (disabled)
[proguard] Number of privatized fields: 0 (disabled)
[proguard] Number of inlined constant fields: 0 (disabled)
[proguard] Number of privatized methods: 0
[proguard] Number of staticized methods: 2
[proguard] Number of finalized methods: 0
[proguard] Number of removed method parameters: 2
[proguard] Number of inlined constant parameters: 1
[proguard] Number of inlined constant return values: 0
[proguard] Number of inlined short method calls: 0
[proguard] Number of inlined unique method calls: 0
[proguard] Number of inlined tail recursion calls: 0
[proguard] Number of merged code blocks: 0
[proguard] Number of variable peephole optimizations: 0
[proguard] Number of arithmetic peephole optimizations: 0 (disabled)
[proguard] Number of cast peephole optimizations: 0
[proguard] Number of field peephole optimizations: 0
[proguard] Number of branch peephole optimizations: 0
[proguard] Number of simplified instructions: 0
[proguard] Number of removed instructions: 21
[proguard] Number of removed local variables: 0
[proguard] Number of removed exception blocks: 0
[proguard] Number of optimized local variable frames: 0
[proguard] Shrinking...
[proguard] Removing unused program classes and class elements...
[proguard] Original number of program classes: 2
[proguard] Final number of program classes: 2
[proguard] Optimizing...
[proguard] Number of finalized classes: 0
[proguard] Number of vertically merged classes: 0 (disabled)
[proguard] Number of horizontally merged classes: 0 (disabled)
[proguard] Number of removed write-only fields: 0 (disabled)
[proguard] Number of privatized fields: 0 (disabled)
[proguard] Number of inlined constant fields: 0 (disabled)
[proguard] Number of privatized methods: 0
[proguard] Number of staticized methods: 0
[proguard] Number of finalized methods: 0
[proguard] Number of removed method parameters: 0
[proguard] Number of inlined constant parameters: 0
[proguard] Number of inlined constant return values: 0
[proguard] Number of inlined short method calls: 0
[proguard] Number of inlined unique method calls: 0
[proguard] Number of inlined tail recursion calls: 0
[proguard] Number of merged code blocks: 0
[proguard] Number of variable peephole optimizations: 0
[proguard] Number of arithmetic peephole optimizations: 0 (disabled)
[proguard] Number of cast peephole optimizations: 0
[proguard] Number of field peephole optimizations: 0
[proguard] Number of branch peephole optimizations: 0
[proguard] Number of simplified instructions: 0
[proguard] Number of removed instructions: 0
[proguard] Number of removed local variables: 0
[proguard] Number of removed exception blocks: 0
[proguard] Number of optimized local variable frames: 0
[proguard] Obfuscating...
[proguard] Printing mapping to [C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf\mapping.txt]...
[proguard] Writing output...
[proguard] Preparing output jar [C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf\postobf.jar]
[proguard] Copying resources from program jar [C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf\original.jar]
[proguard] Printing classes to [C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf\dump.txt]...
-dex-no-obfuscate:
-post-compile:
-dex:
[echo] Converting compiled files and external libraries into C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin\classes.dex...
-package-resources:
[echo] Packaging resources
[aapt] Creating full resource package...
-package-release:
[apkbuilder] Creating HelloAndroid-unsigned.apk for release...
-release-prompt-for-password:
-release-nosign:
release:
[echo] Signing final apk...
[signjar] Signing JAR: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin\HelloAndroid-unsigned.apk to C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin\HelloAndroid-unaligned.apk as ky
[signjar] キーストアのパスワードを入力してください:
[echo] Running zip align on final apk...
[echo] Release Package: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin\HelloAndroid-release.apk
deploy:
[echo] Installing C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin\HelloAndroid-release.apk...
[exec] pkg: /data/local/tmp/HelloAndroid-release.apk
[exec] Success
[exec] 72 KB/s (4211 bytes in 0.057s)
BUILD SUCCESSFUL
Total time: 8 seconds
-dex-obfuscate:のフェーズで[proguard]のログが出力されていれば、ProGuardが適用されています。ログを詳しく見ると、下記のように圧縮、最適化、難読化が行われていることが分かります。
- [proguard] Shrinking...
- [proguard] Optimizing...
- [proguard] Obfuscating...
ProGuard適用前と適用後を比較してみる
実際にProGuardの処理結果を比較して確認してみましょう。
apktoolによるデコンパイル
リエンジニアリングツールであるandroid-apktoolを使って、.apkファイルをデコンパイルし、アプリ内のクラス構成や処理内容を可視化します。
apktoolのシステム要件
- JRE 1.6 (Java Runtime Environment)
- 実行パスにaaptコマンドが設定されていること
apktoolのダウンロード
apktoolのダウンロードページから、OSに対応したファイルをダウンロードして下さい。
apktoolのインストール
- apktool-install-windows-* ファイルのダウンロード(Windowsの場合)
- apktool-* ファイルのダウンロード
- 以上の2つのファイルを適当なディレクトリに展開します。
デコンパイルの実行
$ cd <プロジェクトディレクトリ>/bin/
$ apktool d HelloAndroid-release.apk
デコンパイルの結果、HelloAndroid-release.apkのディレクトリ以下に、HelloAndroid-releaseディレクトリが生成され、下記のようなファイルが生成されます。
- AndroidManifest.xml:アプリのマニフェストファイル
- apktool.yml:apktoolの処理結果
- resディレクトリ:アプリに含まれるリソースファイル
- smaliディレクトリ:アプリの処理手順の解析結果
smaliファイルの比較
最適化や難読化がなされているかどうかは、smaliディレクトリをProGuardを適用しない場合と適用した場合とで見比べてみることで判ります。
ProGuard適用前
ファイル構成:
- HelloAndroid.smali
- HelloAndroid$testClass.smali
- R.smali
- R$attr.smali
- R$layout.smali
- R$string.smali
HelloAndroid.smaliの内容:
.class public Ltest/hello/HelloAndroid;
.super Landroid/app/Activity;
.source "HelloAndroid.java"
# annotations
.annotation system Ldalvik/annotation/MemberClasses;
value = {
Ltest/hello/HelloAndroid$testClass;
}
.end annotation
# direct methods
.method public constructor <init>()V
.locals 0
.prologue
.line 6
invoke-direct {p0}, Landroid/app/Activity;-><init>()V
.line 8
return-void
.end method
.method private testMethod1()V
.locals 3
.prologue
.line 32
const/4 v0, 0x0
.local v0, i:I
const/4 v1, 0x0
.local v1, sum:I
const/16 v2, 0xa
if-ge v0, v2, :cond_0
.line 33
add-int/2addr v1, v0
.line 32
add-int/lit8 v0, v0, 0x1
goto :goto_0
.line 35
return-void
.end method
# virtual methods
.method public onCreate(Landroid/os/Bundle;)V
.locals 1
.parameter "savedInstanceState"
.prologue
.line 23
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
.line 24
const/high16 v0, 0x7f02
invoke-virtual {p0, v0}, Ltest/hello/HelloAndroid;->setContentView(I)V
.line 25
invoke-direct {p0}, Ltest/hello/HelloAndroid;->testMethod1()V
.line 26
new-instance v0, Ltest/hello/HelloAndroid$testClass;
invoke-direct {v0}, Ltest/hello/HelloAndroid$testClass;-><init>()V
.line 27
return-void
.end method
ProGuard適用しない場合、アプリのファイル構成がそのまま解析結果として出力され、クラス・メソッドの構成、コールシーケンスが丸見えになります。また、内部で使用しているクラス(内部クラスを含む)やメソッド名も判るため、何の処理をしているのかが予測しやすい状態です。
ProGuard適用後
ファイル構成:
- a.smali
- HelloAndroid.smali
HelloAndroid.smaliの内容:
.class public Ltest/hello/HelloAndroid;
.super Landroid/app/Activity;
# direct methods
.method public constructor <init>()V
.locals 0
invoke-direct {p0}, Landroid/app/Activity;-><init>()V
return-void
.end method
# virtual methods
.method public onCreate(Landroid/os/Bundle;)V
.locals 1
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
const/high16 v0, 0x7f02
invoke-virtual {p0, v0}, Ltest/hello/HelloAndroid;->setContentView(I)V
new-instance v0, Ltest/hello/a;
invoke-direct {v0}, Ltest/hello/a;-><init>()V
return-void
.end method
a.smaliの内容:
.class final Ltest/hello/a;
.super Ljava/lang/Object;
# direct methods
.method constructor <init>()V
.locals 0
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
ProGuardを適用すると、ファイル構成は「エントリポイントとなるクラスファイル」と「難読化されたクラスファイル」だけとなっており、実際のファイル構成とは異なっています。また、クラスやメソッドの解析に失敗しており、この結果から処理を予測することが難しくなっています。
まとめ
以上のようにProGuardを利用すると簡単な手順で実効性のある難読化を行い、Androidのアプリという知的財産を守ることができます。一方で、JNI経由でネイティブメソッドなどを利用している場合、ProGuardでのビルドに失敗するなど、適用に関わる工数が著しく増大することがあります(いわゆる「ハマる」可能性があるということ)。とはいえ、アプリ中のアルゴリズムや手順、利用している技術が特殊であればあるほど、ProGuardを適用する意味は大きいでしょう。何らかの形でアプリ中の知的財産を守る必要がある場合は、ProGuardの利用を検討してみてはいかがでしょうか。
文責:技術部 植物工場研究G 瀬戸 直喜
Androidとセキュリティ:プログラム難読化ツール - ProGuard
はじめに
このエントリでは、Androidアプリという知的財産を守る方法の一つとして、難読化ツールであるProGuardを紹介し、実際にAndroidアプリに組み込む方法を示します。
ソフトウェアの価値
ソフトウェアでは処理の流れ(アルゴリズム)を記述したソースコードが大きな価値を持つため、(特に商用のソフトウェアは)ソースコードを非公開とし、コンピュータで実行可能な必要最小限の中間コードやネイティブコードに限って配布することで、ソフトウェアビジネスを成立させています。つまり、処理の流れを「隠す」ことにより、その会社(あるいは人)にしか解決できないという「情報に対する付加価値」を与え、ソフトウェアビジネスを成立させていると言えます。一方で、何らかの形でこの「隠した部分」が明らかになり白日の下に晒されると、類似したソフトウェアが流通するなど、もはや処理の流れという情報としての「価値」は失われ、ビジネスとして成立しなくなります。
さて、Androidに限らず、最近のソフトウェアは高級言語(Javaなど)を用いて記述され、翻訳(コンパイル)を通じて最終的にはコンピュータが処理可能な「中間コード」や「ネイティブコード」に変換されます。この変換は言語ごとに定められた「文法」によって翻訳されるため、その翻訳ルールさえ判れば逆翻訳(デコンパイルやデアセンブルと言います)が可能です。つまり、「中間コード」や「ネイティブコード」から逆翻訳を行い、人間が理解可能なソースコードを生成することが可能ということです。
このままでは、ちょっとした知識があればソースコードが推測されてしまいます。その為、商用ソフトなどでは難読化を行い、逆翻訳された場合でもソースコードを読みにくくするなどして、簡単に処理の流れが明らかにならないように対策しています。
難読化とは
先に記した通り、難読化とは「ソースコードを読みにくくすること」です。難読化の手法は様々ありますが、下記のような方法によって、翻訳時にソースコードを可能な限り読みにくくします。
- クラスやメソッド、変数名を省略・無意味化
- コメントの削除
- 空白(行)の削除
また、翻訳時に処理パフォーマンスが良くなるように処理の最適化(繰り返し処理の簡略化、関数・メソッドのインライン化等)が行われますが、その結果、人間にとって理解が困難なコードとなることもあります。
Androidの難読化ツール
Androidのビルドプロセスにも適用可能な難読化ツールが存在します。その名も「ProGuard」。
ProGuardは、Javaのクラスファイルの圧縮・難読化・最適化・前検証を行うツールです。使われていないクラス/メソッド/変数を検知・削除したり、バイトコードを最適化したり、クラスやメソッド名を無意味化したりします。ProGuardは一般的なAndroidアプリケーション開発に用いられるEclipseではなく、CUIベースのビルドツール「Ant」に統合される形で利用できます。Antによって、Androidアプリケーションの翻訳や難読化はもちろん、アプリケーションの署名や端末へのインストールも自動化することができます。
ProGuardの概要
引用:http://proguard.sourceforge.net/manual/introduction.html
ProGuardは、jar/war/zip/ディレクトリなどを入力として、次の4つのステップを経て、最終的にjar/war/zip/ディレクトリへ処理結果を出力します。
- 圧縮ステップ(shrink)
- 最適化ステップ(optimize)
- 難読化ステップ(obfuscate)
- 前検証ステップ(preverify)
圧縮ステップでは、プログラム中の利用されていないクラスやメソッド等を取り除きます。このステップは再帰的に処理されるので、全てのクラス・メソッドが対象となります。
最適化ステップでは、エントリポイントではないクラスやメソッドをprivate/static/final属性に変換、使われていないパラメータは削除され、一部のメソッドはインライン化されます。
難読化ステップでは、エントリポイントではないクラスやメソッドの名前を変更します。エントリポイントとなるクラスやメソッドは、オリジナルの名前でアクセスできるようにするために変更されません。
前検証ステップでは、実行時・ロード時のバイトコードの検証負荷を減らすために、あらかじめ型情報を調査し、その情報をクラスファイル内に添付します。Android(Java6)では使用しません。
開発したプログラムは外部から提供されるライブラリを使用することがありますが、これらはProGuardの処理の対象外となります。
ProGuardの適用
それでは、以下ではAndroidのプロジェクトにProGuardを適用し、難読化&最適化を行う方法について説明しましょう。
システム要件
AndroidのプロジェクトにProGuardを適用する場合、次の条件を満たしている必要があります:
- Android SDKがインストールされていること
- Android SDK Toolsがインストールされていて、少なくともリビジョン7以上であること
- Apache Ant 1.6.5以上(Linux and Mac)/1.7以上(Windows) :Eclipse付属のものを使わず、最新のAntを別途インストールすることが望ましい。
Android SDK Tools Rev.7には、プリコンパイルまたはコンパイル時のユーザー定義処理をサポートするフックを含む、Antビルドルールファイルが同梱されています。
サンプルプロジェクト
今回のサンプルは下記のような比較的シンプルな処理を含みます。ソースコード中には無意味な処理が含まれますが、ProGuardの適用状態が判るようにするためのものです。ここでは処理の内容に注目する必要はありません。
プロジェクトのホームを /HelloAndroidとします。このホームは各自の環境に合わせて読み替えてください。
HelloAndroid/src/test/hello/HelloAndroid.java
package test.hello;
import android.app.Activity;
import android.os.Bundle;
public class HelloAndroid extends Activity
{
public static final class testClass {
testClass() {
methodA(1,"str");
}
private void methodA(int i, String j) {
return;
}
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
testMethod1();
new testClass();
}
private void testMethod1()
{
int i,sum;
for( i = 0, sum = 0; i < 10; i++ ) {
sum += i;
}
}
}
リソースファイル等はデフォルトのものをそのまま使用します。
署名キーの用意
ProGuardには直接的に関係無いが、今回はEclipseを使用してビルド・デプロイしないため、ビルド後に自動でデプロイするために予め署名キーを生成しておくと、後が便利です。既に署名キーを用意している場合は、読み飛ばしてもらって構いません。
$ keytool -genkey -v -keystore release.keystore -alias testhello -keyalg RSA -keysize 4096 -validity 10000
キーストアのパスワードを入力してください: <パスワードを入力>
新規パスワードを再入力してください: <もう一度パスワードを入力>
姓名を入力してください。
[Unknown]: Seto Naoki
組織単位名を入力してください。
[Unknown]:
組織名を入力してください。
[Unknown]: Brilliant Service co.,Ltd.
都市名または地域名を入力してください。
[Unknown]: Tokyo
州名または地方名を入力してください。
[Unknown]:
この単位に該当する 2 文字の国番号を入力してください。
[Unknown]: jp
CN=Seto Naoki, OU=Unknown, O=Brilliant Service co.,Ltd., L=Tokyo, ST=Unknown, C=jp でよろしいですか?
[no]: yes
10,000 日間有効な 4,096 ビットの RSA の鍵ペアと自己署名型証明書 (SHA1withRSA) を生成しています
ディレクトリ名: CN=Seto Naoki, OU=Unknown, O="Brilliant Service co.,Ltd.", L=Tokyo, ST=Unknown, C=jp
<testhello> の鍵パスワードを入力してください。
(キーストアのパスワードと同じ場合は RETURN を押してください): <リターンキーを押す>
[release.keystore を格納中]
プロジェクトの更新
下記のコマンドでAntビルドできるようにプロジェクトを更新します。
cd /HelloAndroid/.. (プロジェクトホームの一つ上)
android update project --path ./HelloAndroid
更新すると下記のファイルが作成されているはずです。
- default.properties
- local.properties
- build.properties
- build.xml
ProGuard用設定ファイルの配置
下記のファイルをプロジェクトのホームに配置します。これらのファイルはオフィシャルブログからもダウンロードできます。
- add-proguard-release.xml
- procfg.txt
/HelloAndroid/add-progurad-release.xml
<property name="obfuscate.dir" value="obf" />
<property name="obfuscate.absolute.dir" location="${obfuscate.dir}" />
<property name="android-jar-preobfuscate" value="${obfuscate.absolute.dir}/original.jar" />
<property name="android-jar-postobfuscate" value="${obfuscate.absolute.dir}/postobf.jar" />
<property name="out.dex.input.absolute.dir" value="${android-jar-postobfuscate}" />
<target name="-post-compile" depends="-dex-obfuscate,-dex-no-obfuscate">
</target>
<target name="-dex-no-obfuscate" unless="build.mode.release">
<mkdir dir="${obfuscate.absolute.dir}" />
<jar basedir="${out.classes.dir}" destfile="${android-jar-postobfuscate}" />
</target>
<target name="-dex-obfuscate" if="build.mode.release">
<property name="proguard-jar" value="${proguard.dir}/proguard.jar" />
<property name="proguard-conf.dir" value="" />
<property name="proguard-conf.absolute.dir" location="${proguard-conf.dir}" />
<property name="proguard-conf" value="${proguard-conf.absolute.dir}/procfg.txt" />
<path id="fullclasspath">
<path refid="android.target.classpath"/>
<pathelement path="${external.libs.dir}"/>
</path>
<property name="libraryjarpath" refid="fullclasspath"/>
<taskdef resource="proguard/ant/task.properties" classpath="${proguard-jar}" />
<mkdir dir="${obfuscate.absolute.dir}" />
<delete file="${android-jar-preobfuscate}"/>
<delete file="${android-jar-postobfuscate}"/>
<jar basedir="${out.classes.dir}" destfile="${android-jar-preobfuscate}" />
<proguard>
@${proguard-conf}
-injars ${android-jar-preobfuscate}
-outjars ${android-jar-postobfuscate}
-libraryjars ${libraryjarpath}
-dump ${obfuscate.absolute.dir}/dump.txt
-printseeds ${obfuscate.absolute.dir}/seeds.txt
-printusage ${obfuscate.absolute.dir}/usage.txt
-printmapping ${obfuscate.absolute.dir}/mapping.txt
</proguard>
</target>
このファイルでは、AntでProGuardを使用する際のパラメータ(出力先ディレクトリなど)の定義、ant_rules_r3で定義されるAntのプリコンパイルルールの書き換え、Ant用ProGuardタスクの追加の定義などを行います。
/HelloAndroid/procfg.txt
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class com.android.vending.licensing.ILicensingService
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
ここでは最適化の設定や難読化をしない設定(-keep*)を行っています。ビルドエラーが発生する場合、設定を変更する必要があります。この辺りのテクニックは以下のサイトが参考になります。
http://d.hatena.ne.jp/hyoromo/20101120/1290216449
ビルド設定ファイルの修正
build.propertiesとbuild.xmlを改修してProGuardに対応させます。
/HelloAndroid/build.properties
標準の設定ファイルに、下記の設定を追加します。
- 自動デプロイのオプション設定(12~16行目)
- 自動署名の設定(18~26行目)
- ProGuardの有効化(33行目)
01 : # This file is used to override default values used by the Ant build system.
02 : #
03 : # This file must be checked in Version Control Systems, as it is
04 : # integral to the build system of your project.
05 :
06 : # This file is only used by the Ant script.
07 :
08 : # You can use this to override default values such as
09 : # 'source.dir' for the location of your java source folder and
10 : # 'out.dir' for the location of your output folder.
11 :
12 : #-d:device/-e:emulator
13 : adb.device.arg=
14 :
15 : #adb install <option>
16 : adb.install.option=-r
17 :
18 : # You can also use it define how the release builds are signed by declaring
19 : # the following properties:
20 : # 'key.store' for the location of your keystore and
21 : # 'key.alias' for the name of the key to use.
22 : # The password will be asked during the build when you use the 'release' target.
23 : key.store=<キーストアのファイルパス(ファイル名含む)>
24 : key.store.password=<キーストアのパスワード>
25 : key.alias=<キー生成時に指定したエイリアス>
26 : key.alias.password=<エイリアスのパスワード>
27 :
28 : # The name of your application package as defined in the manifest.
29 : # Used by the 'uninstall' rule.
30 : application.package=test.hello
31 :
32 : # For ProGuard
33 : proguard.dir=<ProGuardのlibディレクトリまでのパス>
proguard.dirは<ProGuardのインストール先>\libを指定する必要があります。
/HelloAndroid/build.xml
標準の設定ファイルに、下記の設定を追加します。
- ProGuard用設定XMLファイルの読み込み(2~6行目)
- アプリケーションへの自動署名とデプロイタスクの定義(88~103行目)
01 : <?xml version="1.0" encoding="UTF-8"?>
02 : <!DOCTYPE project [
03 : <!ENTITY add-proguard-release SYSTEM "add-proguard-release.xml">
04 : ]>
05 : <project name="HelloAndroid" default="help">
06 : &add-proguard-release;
07 :
08 :
12 : <property file="local.properties" />
13 :
14 :
31 : <property file="build.properties" />
32 :
33 :
37 : <property file="default.properties" />
38 :
39 :
42 : <path id="android.antlibs">
43 : <pathelement path="${sdk.dir}/tools/lib/anttasks.jar" />
44 : <pathelement path="${sdk.dir}/tools/lib/sdklib.jar" />
45 : <pathelement path="${sdk.dir}/tools/lib/androidprefs.jar" />
46 : </path>
47 :
48 : <taskdef name="setup"
49 : classname="com.android.ant.SetupTask"
50 : classpathref="android.antlibs" />
51 :
52 :
54 :
66 :
67 :
68 :
86 : <setup />
87 :
88 :
89 : <macrodef name="install-release-helper">
90 : <sequential>
91 : <echo>Installing ${out.release.file}...</echo>
92 : <exec executable="${adb}" failonerror="true">
93 : <arg line="${adb.device.arg}" />
94 : <arg value="install" />
95 : <arg value="${adb.install.option}" />
96 : <arg path="${out.release.file}" />
97 : </exec>
98 : </sequential>
99 : </macrodef>
101:
101: <target name="deploy" depends="release">
102: <install-release-helper />
103: </target>
104:
105:</project>
ProGuardの適用(ビルド)とデプロイ
ビルドとデプロイは簡単です。アプリを配置したいデバイスを接続し(または、abdコマンドでシミュレータ上にデバイスを作成しておいて)、下記のコマンドを実行します。
$ ant deploy
これまでに示した定義ファイルの記述が正しければ、ProGuardが適用された状態でビルドされ、アプリへの自動署名が行われて端末にアプリがインストールされます。
Buildfile: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\build.xml
[setup] Android SDK Tools Revision 7
[setup] Project Target: Google APIs
[setup] Vendor: Google Inc.
[setup] Platform Version: 1.5
[setup] API level: 3
[setup]
[setup] ------------------
[setup] Resolving library dependencies:
[setup] ------------------
[setup] Ordered libraries:
[setup] ------------------
[setup]
[setup] WARNING: No minSdkVersion value set. Application will install on all Android versions.
[setup]
[setup] Importing rules file: tools\ant\ant_rules_r3.xml
-set-release-mode:
-dirs:
[echo] Creating output directories if needed...
[mkdir] Created dir: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin
[mkdir] Created dir: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\gen
[mkdir] Created dir: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin\classes
-pre-build:
-resource-src:
[echo] Generating R.java / Manifest.java from the resources...
-aidl:
[echo] Compiling aidl files into Java classes...
-pre-compile:
compile:
[javac] C:\android-sdk-windows\tools\ant\ant_rules_r3.xml:336: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
[javac] Compiling 2 source files to C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin\classes
-dex-obfuscate:
[mkdir] Created dir: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf
[jar] Building jar: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf\original.jar
[proguard] ProGuard, version 4.5.1
[proguard] Reading input...
[proguard] Reading program jar [C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf\original.jar]
[proguard] Reading library jar [C:\android-sdk-windows\platforms\android-3\android.jar]
[proguard] Reading library jar [C:\android-sdk-windows\add-ons\addon_google_apis_google_inc_3\libs\maps.jar]
[proguard] Reading library directory [C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\libs]
[proguard] Initializing...
[proguard] Note: the configuration refers to the unknown class 'com.android.vending.licensing.ILicensingService'
[proguard] Note: there were 1 references to unknown classes.
[proguard] You should check your configuration for typos.
[proguard] Ignoring unused library classes...
[proguard] Original number of library classes: 2398
[proguard] Final number of library classes: 92
[proguard] Printing kept classes, fields, and methods...
[proguard] Shrinking...
[proguard] Printing usage to [C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf\usage.txt]...
[proguard] Removing unused program classes and class elements...
[proguard] Original number of program classes: 6
[proguard] Final number of program classes: 2
[proguard] Optimizing...
[proguard] Number of finalized classes: 0
[proguard] Number of vertically merged classes: 0 (disabled)
[proguard] Number of horizontally merged classes: 0 (disabled)
[proguard] Number of removed write-only fields: 0 (disabled)
[proguard] Number of privatized fields: 0 (disabled)
[proguard] Number of inlined constant fields: 0 (disabled)
[proguard] Number of privatized methods: 0
[proguard] Number of staticized methods: 2
[proguard] Number of finalized methods: 0
[proguard] Number of removed method parameters: 2
[proguard] Number of inlined constant parameters: 1
[proguard] Number of inlined constant return values: 0
[proguard] Number of inlined short method calls: 0
[proguard] Number of inlined unique method calls: 0
[proguard] Number of inlined tail recursion calls: 0
[proguard] Number of merged code blocks: 0
[proguard] Number of variable peephole optimizations: 0
[proguard] Number of arithmetic peephole optimizations: 0 (disabled)
[proguard] Number of cast peephole optimizations: 0
[proguard] Number of field peephole optimizations: 0
[proguard] Number of branch peephole optimizations: 0
[proguard] Number of simplified instructions: 0
[proguard] Number of removed instructions: 21
[proguard] Number of removed local variables: 0
[proguard] Number of removed exception blocks: 0
[proguard] Number of optimized local variable frames: 0
[proguard] Shrinking...
[proguard] Removing unused program classes and class elements...
[proguard] Original number of program classes: 2
[proguard] Final number of program classes: 2
[proguard] Optimizing...
[proguard] Number of finalized classes: 0
[proguard] Number of vertically merged classes: 0 (disabled)
[proguard] Number of horizontally merged classes: 0 (disabled)
[proguard] Number of removed write-only fields: 0 (disabled)
[proguard] Number of privatized fields: 0 (disabled)
[proguard] Number of inlined constant fields: 0 (disabled)
[proguard] Number of privatized methods: 0
[proguard] Number of staticized methods: 0
[proguard] Number of finalized methods: 0
[proguard] Number of removed method parameters: 0
[proguard] Number of inlined constant parameters: 0
[proguard] Number of inlined constant return values: 0
[proguard] Number of inlined short method calls: 0
[proguard] Number of inlined unique method calls: 0
[proguard] Number of inlined tail recursion calls: 0
[proguard] Number of merged code blocks: 0
[proguard] Number of variable peephole optimizations: 0
[proguard] Number of arithmetic peephole optimizations: 0 (disabled)
[proguard] Number of cast peephole optimizations: 0
[proguard] Number of field peephole optimizations: 0
[proguard] Number of branch peephole optimizations: 0
[proguard] Number of simplified instructions: 0
[proguard] Number of removed instructions: 0
[proguard] Number of removed local variables: 0
[proguard] Number of removed exception blocks: 0
[proguard] Number of optimized local variable frames: 0
[proguard] Obfuscating...
[proguard] Printing mapping to [C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf\mapping.txt]...
[proguard] Writing output...
[proguard] Preparing output jar [C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf\postobf.jar]
[proguard] Copying resources from program jar [C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf\original.jar]
[proguard] Printing classes to [C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\obf\dump.txt]...
-dex-no-obfuscate:
-post-compile:
-dex:
[echo] Converting compiled files and external libraries into C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin\classes.dex...
-package-resources:
[echo] Packaging resources
[aapt] Creating full resource package...
-package-release:
[apkbuilder] Creating HelloAndroid-unsigned.apk for release...
-release-prompt-for-password:
-release-nosign:
release:
[echo] Signing final apk...
[signjar] Signing JAR: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin\HelloAndroid-unsigned.apk to C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin\HelloAndroid-unaligned.apk as ky
[signjar] キーストアのパスワードを入力してください:
[echo] Running zip align on final apk...
[echo] Release Package: C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin\HelloAndroid-release.apk
deploy:
[echo] Installing C:\Users\brilliantservice.brilliant_vaio\anttest\HelloAndroid\bin\HelloAndroid-release.apk...
[exec] pkg: /data/local/tmp/HelloAndroid-release.apk
[exec] Success
[exec] 72 KB/s (4211 bytes in 0.057s)
BUILD SUCCESSFUL
Total time: 8 seconds
-dex-obfuscate:のフェーズで[proguard]のログが出力されていれば、ProGuardが適用されています。ログを詳しく見ると、下記のように圧縮、最適化、難読化が行われていることが分かります。
- [proguard] Shrinking...
- [proguard] Optimizing...
- [proguard] Obfuscating...
ProGuard適用前と適用後を比較してみる
実際にProGuardの処理結果を比較して確認してみましょう。
apktoolによるデコンパイル
リエンジニアリングツールであるandroid-apktoolを使って、.apkファイルをデコンパイルし、アプリ内のクラス構成や処理内容を可視化します。
apktoolのシステム要件
- JRE 1.6 (Java Runtime Environment)
- 実行パスにaaptコマンドが設定されていること
apktoolのダウンロード
apktoolのダウンロードページから、OSに対応したファイルをダウンロードして下さい。
apktoolのインストール
- apktool-install-windows-* ファイルのダウンロード(Windowsの場合)
- apktool-* ファイルのダウンロード
- 以上の2つのファイルを適当なディレクトリに展開します。
デコンパイルの実行
$ cd <プロジェクトディレクトリ>/bin/
$ apktool d HelloAndroid-release.apk
デコンパイルの結果、HelloAndroid-release.apkのディレクトリ以下に、HelloAndroid-releaseディレクトリが生成され、下記のようなファイルが生成されます。
- AndroidManifest.xml:アプリのマニフェストファイル
- apktool.yml:apktoolの処理結果
- resディレクトリ:アプリに含まれるリソースファイル
- smaliディレクトリ:アプリの処理手順の解析結果
smaliファイルの比較
最適化や難読化がなされているかどうかは、smaliディレクトリをProGuardを適用しない場合と適用した場合とで見比べてみることで判ります。
ProGuard適用前
ファイル構成:
- HelloAndroid.smali
- HelloAndroid$testClass.smali
- R.smali
- R$attr.smali
- R$layout.smali
- R$string.smali
HelloAndroid.smaliの内容:
.class public Ltest/hello/HelloAndroid;
.super Landroid/app/Activity;
.source "HelloAndroid.java"
# annotations
.annotation system Ldalvik/annotation/MemberClasses;
value = {
Ltest/hello/HelloAndroid$testClass;
}
.end annotation
# direct methods
.method public constructor <init>()V
.locals 0
.prologue
.line 6
invoke-direct {p0}, Landroid/app/Activity;-><init>()V
.line 8
return-void
.end method
.method private testMethod1()V
.locals 3
.prologue
.line 32
const/4 v0, 0x0
.local v0, i:I
const/4 v1, 0x0
.local v1, sum:I
const/16 v2, 0xa
if-ge v0, v2, :cond_0
.line 33
add-int/2addr v1, v0
.line 32
add-int/lit8 v0, v0, 0x1
goto :goto_0
.line 35
return-void
.end method
# virtual methods
.method public onCreate(Landroid/os/Bundle;)V
.locals 1
.parameter "savedInstanceState"
.prologue
.line 23
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
.line 24
const/high16 v0, 0x7f02
invoke-virtual {p0, v0}, Ltest/hello/HelloAndroid;->setContentView(I)V
.line 25
invoke-direct {p0}, Ltest/hello/HelloAndroid;->testMethod1()V
.line 26
new-instance v0, Ltest/hello/HelloAndroid$testClass;
invoke-direct {v0}, Ltest/hello/HelloAndroid$testClass;-><init>()V
.line 27
return-void
.end method
ProGuard適用しない場合、アプリのファイル構成がそのまま解析結果として出力され、クラス・メソッドの構成、コールシーケンスが丸見えになります。また、内部で使用しているクラス(内部クラスを含む)やメソッド名も判るため、何の処理をしているのかが予測しやすい状態です。
ProGuard適用後
ファイル構成:
- a.smali
- HelloAndroid.smali
HelloAndroid.smaliの内容:
.class public Ltest/hello/HelloAndroid;
.super Landroid/app/Activity;
# direct methods
.method public constructor <init>()V
.locals 0
invoke-direct {p0}, Landroid/app/Activity;-><init>()V
return-void
.end method
# virtual methods
.method public onCreate(Landroid/os/Bundle;)V
.locals 1
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
const/high16 v0, 0x7f02
invoke-virtual {p0, v0}, Ltest/hello/HelloAndroid;->setContentView(I)V
new-instance v0, Ltest/hello/a;
invoke-direct {v0}, Ltest/hello/a;-><init>()V
return-void
.end method
a.smaliの内容:
.class final Ltest/hello/a;
.super Ljava/lang/Object;
# direct methods
.method constructor <init>()V
.locals 0
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
ProGuardを適用すると、ファイル構成は「エントリポイントとなるクラスファイル」と「難読化されたクラスファイル」だけとなっており、実際のファイル構成とは異なっています。また、クラスやメソッドの解析に失敗しており、この結果から処理を予測することが難しくなっています。
まとめ
以上のようにProGuardを利用すると簡単な手順で実効性のある難読化を行い、Androidのアプリという知的財産を守ることができます。一方で、JNI経由でネイティブメソッドなどを利用している場合、ProGuardでのビルドに失敗するなど、適用に関わる工数が著しく増大することがあります(いわゆる「ハマる」可能性があるということ)。とはいえ、アプリ中のアルゴリズムや手順、利用している技術が特殊であればあるほど、ProGuardを適用する意味は大きいでしょう。何らかの形でアプリ中の知的財産を守る必要がある場合は、ProGuardの利用を検討してみてはいかがでしょうか。
文責:技術部 植物工場研究G 瀬戸 直喜
