Dalvik実行可能ファイル (DEX)に別のDEXが埋め込まれていた!

Tim StrazzereのAndroid CrackMe
Tim StrazzereのAndroid CrackMe

Androidの課題の時間です。Tim StrazzereがBlackHatでおもしろいAndroid CrackMeを提供してくれました。投稿許可をもらいましたので、以下が私の見つけた解答のネタバレになります。

パッケージはdroid-with-a-big-ego.apkという名前になっており、APKToolやBaksmaliでは処理が難しいです。

I: Baksmaling...
Exception in thread "main" org.jf.dexlib.Util.ExceptionWithContext: The header size is not the expected value (0x70)
        at org.jf.dexlib.Util.ExceptionWithContext.withContext(ExceptionWithContext.java:54)

私はdex2jarを使ってやってみましたが、代わりにded やAndroguardといったツールを使うこともできます。
この課題のメインクラスはAPKryptActivityです。このクラスが最初にやることの1つがinitializeMethods()というメソッドを呼び出すことです。おもしろいスタートですね。

this.dexFile = new DexFile(getApplicationContext());
localClass = this.dexFile.getClass("dont/decompile/me/Crack");
..
Method[] arrayOfMethod = localClass.getMethods();
Class[] arrayOfClass1 = new Class[1];
arrayOfClass1[0] = Context.class;
this.isEmulator = localClass.getMethod("isEmulator", arrayOfClass1);
..
Class[] arrayOfClass2 = new Class[1];
arrayOfClass2[0] = String.class;
this.checkPassword = localClass.getMethod("checkPassword", arrayOfClass2);

リフレクションを使ってisEmulator()メソッドとcheckPassword()メソッドをCrackという名前のクラスから読み込んでいます。DexFileは自作クラスです。では見てみましょう。

DexFileコンストラクタ
DexFileコンストラクタ

このコンストラクタはまずWorkerというクラスのインスタンスを作成し、そのクラス上でgetPackedSectionLength()を呼び出します。その結果は整数で格納し、もし数値がマイナス、あるいはゼロの場合は例外を発生させます。そしてこのコンストラクタは標準クラスであるdalvik.system.DexFileを読み込み、initializeMethods()を呼び出します。このメソッドを見てみると、確かにこのクラスからメソッドを読み込んでいます。ですがパブリックのメソッドではありません! 特に、プライベートネイティブのメソッドであるdefineClass()とopenDexFile()を探しています(AOSPでAndroidのソースコードを見て、libcore/dalvik/src/main/java/dalvik/system/DexFile.javaというファイルを確認してください)。

Method[] arrayOfMethod = Class.forName("dalvik.system.DexFile").getDeclaredMethods();
..
// set defineClass (class field) to the method whose name matches "defineClass"
// bypass access to method
defineClass.setAccessible(true);

次に、このDexFileコンストラクタはlocalWorkerオブジェクトでgetData()を呼び出します。このメソッドもおもしろいのです。次をご覧ください。

Worker.getData()メソッド
Worker.getData()メソッド

このメソッドはclasses.dexカレントから読み込みますが、最初の112バイトをスキップします。そしてXOR key 0xD1を適用して読み込んだものを解読します。これは手動で行うことも可能です。

$ dd if=classes.dex of=work.dex bs=1 skip=112
18491+0 records in
18491+0 records out
18491 bytes (18 kB) copied, 0.0651274 s, 284 kB/s
$ perl -ne 'print pack "C*", map {$_^0xd1} unpack "C*", $_' work.dex > decrypted.dex
$ hexdump -C decrypted.dex
00000000  64 65 78 0a 30 33 35 00  ca 5c c8 a5 08 01 d2 8d  |dex.035..\......|
00000010  9b 4c 50 5c 2d 8c ed 6d  6f 98 2c b9 09 2b cd 52  |.LP\-..mo.,..+.R|
...

何が見つかったでしょうか? また別のDexファイルです! そうです。メインのDexファイルにDexファイルが埋め込んであるのです。

この埋め込まれたDexファイルを分析してみましょう。checkPasswordというメソッドが入っていますが、残念ながらこれは正しく逆コンパイルできません。

checkPasswordは正しく逆コンパイルしない
checkPasswordは正しく逆コンパイルしない

気を落とさず、Smaliコードを調べてみましょう。

.method public static checkPassword(Ljava/lang/String;)Z
    .registers 7
    .parameter "password"

    .prologue
    .line 10
    const/16 v4, 0x11

    new-array v3, v4, [B

    fill-array-data v3, :array_3a

    .line 14
    .local v3, secret:[B
    const/4 v1, 0x0

この部分はarray_3a のデータとともにsecretというバイトアレイを初期化しています。このメソッドをもう少しパースし、これ以上トリックがないか確かめてみましょう。

.local v1, decompiling:Z
if-eqz v1, :cond_19

ブール型の逆コンパイルです。フォールスの場合、cond_19へジャンプします。

:cond_19
new-instance v4, Ljava/lang/String;
invoke-direct {v4, v3}, Ljava/lang/String;->([B)V
invoke-virtual {p0, v4}, Ljava/lang/String;->compareTo(Ljava/lang/String;)I
move-result v4
if-nez v4, :cond_38
...

この部分は入力ストリング(エンドユーザーが提供するパスワード)をthe secret (v3)と比較しています。解読アルゴリズムはありませんので、secretはおそらくプレーンテキストの中でしょう。

   :array_3a
    .array-data 0x1
        0x72t
        0x40t
        0x69t
        0x6et
        0x62t
        0x6ft
        0x77t
        0x73t
        0x20t
        0x6et
        0x20t
        0x70t
        0x77t
        0x6et
        0x69t
        0x65t
        0x73t

ASCIIでは、これは「r@inbows n pwnies」となる(引用符除く)

Androidのエミュレータにこのパスワードを入力することはできません。コードがエミュレータを検出するからです。このタスクはisEmulator()という静的メソッドによって行われます。これがro.kernel.qemu、ro.hardware、gsm.version.ril-implといったプロパティのためにデフォルトの数値を照合します。実際のIce Cream Sandwich(ICS)の電話でcrackmeを実行するか、エミュレータをハックするか、ですね。パスワードがわかったので(それに他にもやることがたくさんあるので...)、お遊びはここで終わることにしました。

- the Crypto Girl