Androidとセキュリティ:プログラム難読化ツール - ProGuard
ソフトウェアの価値
ソフトウェアでは処理の流れ(アルゴリズム)を記述したソースコードが大きな価値を持つため、(特に商用のソフトウェアは)ソースコードを非公開とし、コンピュータで実行可能な必要最小限の中間コードやネイティブコードに限って配布することで、ソフトウェアビジネスを成立させています。つまり、処理の流れを「隠す」ことにより、その会社(あるいは人)にしか解決できないという「情報に対する付加価値」を与え、ソフトウェアビジネスを成立させていると言えます。一方で、何らかの形でこの「隠した部分」が明らかになり白日の下に晒されると、類似したソフトウェアが流通するなど、もはや処理の流れという情報としての「価値」は失われ、ビジネスとして成立しなくなります。
さて、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
<!-- Proguard Properties --> <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}" /> <!-- replaces the post-compile step from ant_rules_r3 --> <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> <!-- Converts this project's .class files into .dex files --> <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"/> <!-- Add Proguard Task --> <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は
/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 : <!-- The local.properties file is created and updated by the 'android' 09 : tool. 10 : It contains the path to the SDK. It should *NOT* be checked into 11 : Version Control Systems. --> 12 : <property file="local.properties" /> 13 : 14 : <!-- The build.properties file can be created by you and is never touched 15 : by the 'android' tool. This is the place to change some of the 16 : default property values used by the Ant rules. 17 : Here are some properties you may want to change/update: 18 : 19 : source.dir 20 : The name of the source directory. Default is 'src'. 21 : out.dir 22 : The name of the output directory. Default is 'bin'. 23 : 24 : Properties related to the SDK location or the project target should 25 : be updated using the 'android' tool with the 'update' action. 26 : 27 : This file is an integral part of the build system for your 28 : application and should be checked into Version Control Systems. 29 : 30 : --> 31 : <property file="build.properties" /> 32 : 33 : <!-- The default.properties file is created and updated by the 'android' 34 : tool, as well as ADT. 35 : This file is an integral part of the build system for your 36 : application and should be checked into Version Control Systems. --> 37 : <property file="default.properties" /> 38 : 39 : <!-- Custom Android task to deal with the project target, and import the 40 : proper rules. 41 : This requires ant 1.6.0 or above. --> 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 : <!-- extension targets. Uncomment the ones where you want to do custom work 53 : in between standard targets --> 54 : <!-- 55 : <target name="-pre-build"> 56 : </target> 57 : <target name="-pre-compile"> 58 : </target> 59 : 60 : [This is typically used for code obfuscation. 61 : Compiled code location: ${out.classes.absolute.dir} 62 : If this is not done in place, override ${out.dex.input.absolute.dir}] 63 : <target name="-post-compile"> 64 : </target> 65 : --> 66 : 67 : 68 : <!-- Execute the Android Setup task that will setup some properties 69 : specific to the target, and import the build rules files. 70 : 71 : The rules file is imported from 72 : <SDK>/platforms/<target_platform>/ant/ant_rules_r#.xml 73 : 74 : To customize existing targets, there are two options: 75 : - Customize only one target: 76 : - copy/paste the target into this file, *before* the 77 : <setup> task. 78 : - customize it to your needs. 79 : - Customize the whole script. 80 : - copy/paste the content of the rules files (minus the top node) 81 : into this file, *after* the <setup> task 82 : - disable the import of the rules by changing the setup task 83 : below to <setup import="false" />. 84 : - customize to your needs. 85 : --> 86 : <setup /> 87 : 88 : <!-- 自動.apk生成及びインストール --> 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> 100: 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のダウンロード
apktoolのダウンロードページから、OSに対応したファイルをダウンロードして下さい。
apktoolのインストール
デコンパイルの実行
$ cd <プロジェクトディレクトリ>/bin/ $ apktool d HelloAndroid-release.apk
デコンパイルの結果、HelloAndroid-release.apkのディレクトリ以下に、HelloAndroid-releaseディレクトリが生成され、下記のようなファイルが生成されます。
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 :goto_0 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 :cond_0 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 瀬戸 直喜