アキ 「こんにちわー。クウちゃんも久しぶりー♪」
クウ 「あ、こんにちは。よろしくお願いします」
アキ 「それじゃあ、早速だけど、必要な情報とかの意識合わせしましょー」
ナツ 「了解。事前に言われてた資料は一式そろえておいたよ」
クウは、仕様や動作などを簡単に説明し、Androidアプリケーションをアキに渡した。
アキ 「そいじゃあ、今日は戻ったらちょっとだけ見てみて、明日から本格的に始めるね」
クウ 「はい!」
アキ 「細かいのは後にすると思うけど、致命的な問題があったらすぐ教えるようにするね」
クウ 「よろしくです!」
アキ 「よろしくー」
ナツ 「じゃあ、次は今週末かな?」
アキ 「そうだね。また来まーす♪ バイバイ」
クウ 「1週間くらいかかるんですねー。けど、普通のテストの工数とか考えたら時間かかるのは当たり前かぁ〜」
ナツ 「そうだね。それでも、今回はちょっと無理言って結構期間短くしてもらったんだけどね〜」
クウ 「何か問題出ちゃったらリリースまでに直せるのかな……」
ナツ 「リリース日はちょっとずらせないし、頑張って直すか、許容できるかとか考えないとだね」
クウ 「何も問題が出ませんように……」
次の日クウが出社すると、アキから早速メールが来ていた。
ナツさん、クウさんアキです。
お疲れ様です。
昨日もらったアプリをちょっと解析してみたんだけれど、暗号用のキーっぽいのが簡単に抜ける状態だったので、現象を添付にまとめておきました。
難読化はされてたみたいだけど、別の対策も取っておいたほうがいいかもですね。
添付のパスワードは、打ち合わせの時に決めたやつでよろしくです。
クウ 「……」
ナツ 「……メール来てるね」
クウ 「昨日の夕方に渡したのに、もうそこまで見れちゃってるんですね。ひぃー」
ナツ 「ある程度は想定してたけど、こんなすぐに解析できちゃうもんなんだね。困るなぁ。けど、まだリリースまで時間があるのが不幸中の幸いか……」
クウ 「資料確認します!」
クウは早速、アキにもらった資料に目を通した。資料には、逆コンパイルの仕方や、実際にAndroidアプリケーションを逆コンパイルした結果などが記載されていた。
実際に、Androidアプリケーションの逆コンパイルを行う方法だが、フリーのアプリケーションなどを利用して簡単にできる環境が整っている状態だ。
Androidアプリケーションの実行ファイルであるapkファイルは、実際にはアーカイブファイルと類似の形式であり、jar形式やzip形式として展開可能である。apkファイルを展開すると、以下のようなファイル構成になる(アプリケーションによっては若干異なる場合がある)。
このうち特に重要なのは、文字列定義などが含まれるresフォルダやコンパイルされたソースコードであるdexファイルだ。
resフォルダに関しては、展開した時点ではバイナリ形式だが、十分に解析可能である(XML形式などの元の定義ファイル形式に戻すツールも公開されている)。また、dexファイルに関しても、バイナリになってはいるが、変数などの文字列は判別が可能である。
実際のソースコードを例に、どのようにソースコードの内容が見えてしまうかを示してみよう。
■サンプルコード:元のJavaコード
//作成者:クウ //作成日:yyyy/mm/dd package com.example.helloworld; import android.app.Activity; import android.os.Bundle; public class AndroidHelloWorldActivity extends Activity { //暗号化のキー情報 private String secret_key = "kuu#secret_key!!"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); System.out.println("System.out.printlnの出力テスト"); //暗号化テスト SampleClass.sampleMethod(secret_key, "暗号化が必要な文字列"); } }
上記のコードを元に、難読化を行わずにapkファイルを作成してみたとする。そのファイルを逆コンパイルし、ソースコードの復元を試みた結果は以下のようになる。
■サンプルコード:難読化されていないアプリケーションを逆コンパイルした結果
package com.example.helloworld; import android.app.Activity; import android.os.Bundle; import java.io.PrintStream; public class AndroidHelloWorldActivity extends Activity { private String secret_key = "kuu#secret_key!!"; public void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); setContentView(2130903040); System.out.println("System.out.printlnの出力テスト"); SampleClass.sampleMethod(this.secret_key, "暗号化が必要な文字列"); } }
この2つのコードを比較してみると、記述の並び順やコメントの有無といった細かなところこそ異なっているものの、ほぼ完全に元のコードを復元できている状態だ。
これに対して、難読化を行ったapkファイルを逆コンパイルし、ソースコードの復元を試みた場合は以下のようになる。
■サンプルコード:元のJavaコード
package com.example.helloworld; import a; import android.app.Activity; import android.os.Bundle; import java.io.PrintStream; public class AndroidHelloWorldActivity extends Activity { private String a = "kuu#secret_key!!"; public void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); setContentView(2130903040); System.out.println("System.out.printlnの出力テスト"); a.a(this.a, "暗号化が必要な文字列"); } }
元のコードでは「SampleClass」や「sampleMethod」といった具合に記載されていたクラス名やメソッド名が、難読化した場合は、特別な意味を持たない「a」などに変換されている。難読化を行っていない場合に比べ、逆コンパイルしたコードからは処理内容が判別しにくくなっていることが分かる。
しかし、難読化されるのは自作のクラス名/メソッド名のみである。そのため、上記コードの例でも示しているように、「System.out.println」のようなAndroid側で用意されているAPIについては、そのまま判別可能な形で逆コンパイルされてしまう。
また、ハードコードされている定数についても難読化されず、そのままの形で閲覧可能であることにも注意する必要がある(リソース内のXML内に記述した場合についても同様に閲覧可能である)。
関連記事
Androidアプリの解読・改ざんを防ぐ難読化ツールとは
http://www.atmarkit.co.jp/fsmart/articles/android_int02/01.html
クウが記述したコードでは、暗号に利用するキーがハードコードされてしまっていたため、アキにすぐに解析されてしまったのである。
クウ 「むむ……これは困る……」
ナツ 「逆コンパイルできるらしいっていうのは知ってはいたけれど、こうやって実際に簡単にやられちゃうと悩ましいね……」
クウ 「対策ってどうするんだろ、これ」
ナツ 「次のページに書いてあるみたいだよ」
Copyright © ITmedia, Inc. All Rights Reserved.