【Android】APKをデコンパイルしてみる


## はじめに
最近Android開発を始めて、絶賛Kotlin勉強中の者です。今回ソースコードを難読化してbuildすることがあり、本当に難読化できているのかを確かめてみたかったので、デコンパイルすることにしました。ただ、なかなか思うようにデコンパイルができなかったので、成功した方法を備忘録として残しておきます。

今回は、下図の通り、build前のソースコードをKotlinで書いたので、`.kt`ファイルをbuildして`.apk`ファイルになったAndroidのコードをデコンパイルし、`.java`ファイルを作成しました。

![デコンパイル図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/5645baef-472a-2373-a71b-fe26d1c46f29.png)


## 環境・バージョン

+ macOS Catalina バージョン 10.15.6

## 失敗した方法
調べるとよく出てくる`dex2jar`と`jad`を使う方法です。
この方法が定番なのかなと思ったので、この方法でデコンパイルをやってみましたが、わたしの場合上手くいきませんでした。

`dex2jar`やファイルを展開する手順を踏むことで、`.class`ファイルが生成されます。
手順詳細は[こちら](#失敗した方法dex2jarjad手順詳細)へ。


その後、`jad`を使って`.class`を`.java`にデコンパイルします。

```
$ jad hoge.class
```

すると、次のようなエラーが出て、デコンパイルが進まなくなってしまいました。

```
Bad CPU type in executable
```

### 考えられる原因

調べた限り、確実な情報は出てこなかったので、推測です。

+ 現在のMac OSでは64bitのアプリケーションしか動作しないが、jadは32bitのツール？
+ jadは少し前からアップデートが止まっているみたい？

上記みたいなところが原因かなと思います。
きっと他にツールがあるはず、**諦めます！**

## 成功した方法

`jadx`を使う方法を使うと簡単にデコンパイルすることができました。
この方法はJDKが入っていなくても実行できます。

### インストール

macだとHomebrewを使って簡単にインストールすることができます。

```
$ brew install jadx
```

### 実行

実行はこのコマンドのみ！
特にPATHを通すこともなく、どこからでも開くことができます。

```
$ jadx-gui
```

実行すると、下記のような画面が開くので、デコンパイルしたいapkファイルを開きます。

![スクリーンショット 2020-10-19 21.04.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/78d88d02-0104-df9a-3e82-4a37ca9181ff.png)


これでデコンパイルは完了です。
非常に簡単ですね！

### 実行結果
#### 元のコード(.kt)

```kotlin

package com.example.testqiita

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var message = findViewById<TextView>(R.id.text)
        val buttonEnglish = findViewById<Button>(R.id.button_en)
        val buttonJapanese = findViewById<Button>(R.id.button_jp)


        buttonEnglish.setOnClickListener {
            message.setText("Hello World!")
        }

        buttonJapanese.setOnClickListener {
            message.setText("こんにちは 世界!")
        }
    }

}

```
#### デコンパイルしたコード(.java)

ファイルを開くと、下記のようなディレクトリ構成で出力されます。

![スクリーンショット 2020-10-12 23.12.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/5ba72cf6-c9b0-c4d2-b7de-a9dbd51d1026.png)


`MainActivity`は下記のように3つのファイルに分かれていますが、同じ処理が読み取れることがわかります。

```java:MainActivity

package com.example.testqiita;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import kotlin.Metadata;
import kotlin.jvm.internal.Ref;

@Metadata(bv = {1, 0, 3}, d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0012\u0010\u0003\u001a\u00020\u00042\b\u0010\u0005\u001a\u0004\u0018\u00010\u0006H\u0014¨\u0006\u0007"}, d2 = {"Lcom/example/testqiita/MainActivity;", "Landroidx/appcompat/app/AppCompatActivity;", "()V", "onCreate", "", "savedInstanceState", "Landroid/os/Bundle;", "app_debug"}, k = 1, mv = {1, 1, 16})
/* compiled from: MainActivity.kt */
public final class MainActivity extends AppCompatActivity {
    /* access modifiers changed from: protected */
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView((int) R.layout.activity_main);
        Ref.ObjectRef message = new Ref.ObjectRef();
        message.element = (TextView) findViewById(R.id.text);
        ((Button) findViewById(R.id.button_en)).setOnClickListener(new MainActivity$onCreate$1(message));
        ((Button) findViewById(R.id.button_jp)).setOnClickListener(new MainActivity$onCreate$2(message));
    }
}
```

```java:MainActivity\$onCreate\$1

package com.example.testqiita;

import android.view.View;
import android.widget.TextView;
import kotlin.Metadata;
import kotlin.jvm.internal.Ref;

@Metadata(bv = {1, 0, 3}, d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\u0010\u0000\u001a\u00020\u00012\u000e\u0010\u0002\u001a\n \u0004*\u0004\u0018\u00010\u00030\u0003H\n¢\u0006\u0002\b\u0005"}, d2 = {"<anonymous>", "", "it", "Landroid/view/View;", "kotlin.jvm.PlatformType", "onClick"}, k = 3, mv = {1, 1, 16})
/* compiled from: MainActivity.kt */
final class MainActivity$onCreate$1 implements View.OnClickListener {
    final /* synthetic */ Ref.ObjectRef $message;

    MainActivity$onCreate$1(Ref.ObjectRef objectRef) {
        this.$message = objectRef;
    }

    public final void onClick(View it) {
        ((TextView) this.$message.element).setText("Hello World!");
    }
}
```

```java:MainActivity\$onCreate\$2

package com.example.testqiita;

import android.view.View;
import android.widget.TextView;
import kotlin.Metadata;
import kotlin.jvm.internal.Ref;

@Metadata(bv = {1, 0, 3}, d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\u0010\u0000\u001a\u00020\u00012\u000e\u0010\u0002\u001a\n \u0004*\u0004\u0018\u00010\u00030\u0003H\n¢\u0006\u0002\b\u0005"}, d2 = {"<anonymous>", "", "it", "Landroid/view/View;", "kotlin.jvm.PlatformType", "onClick"}, k = 3, mv = {1, 1, 16})
/* compiled from: MainActivity.kt */
final class MainActivity$onCreate$2 implements View.OnClickListener {
    final /* synthetic */ Ref.ObjectRef $message;

    MainActivity$onCreate$2(Ref.ObjectRef objectRef) {
        this.$message = objectRef;
    }

    public final void onClick(View it) {
        ((TextView) this.$message.element).setText("こんにちは 世界!");
    }
}
```

## おわりに
いろいろ躓きましたが、結果的に簡単にデコンパイルできることがわかりました。こんなに簡単にソースの中身を見ることってできるんですね。びっくりです。同時に、難読化や堅牢化の必要性も改めて感じました。
堅牢化できるツールってすごく高価なようですね。。なので、リバースエンジニアリングされることを想定して被害が最小限で済むような設計をする必要がありますし、そのあたりは今後勉強しないといけないところだなと思いました。


ちなみに、Windowsでもjadxは使えるそうです！（Windows環境がなかったので、試せていません。）

+ [AndroidのAPKファイルを逆コンパイルする [jadx/windows版]](https://www.petitmonte.com/java/android_decompile.html)

### 失敗した方法（dex2jar、jad）・手順詳細

それぞれのツールのインストール、デコンパイルの方法の詳細を一応残しておきます。

#### dex2jar

dex2jarのインストールとPATHの設定を行います。
このツールは`.dex`を`.jar`（`.class`を圧縮したファイル）に変換するために使うものです。

```
$ brew install dex2jar

$ echo 'export PATH="$PATH:/usr/local/Cellar/dex2jar/2.0/bin"' >> ~/.bash_profile
$ source ~/.bash_profile
```

#### jad

jadのインストールを行います。
このツールは`.class`を`.java`に変換するために使うものです。

インストールに必要なcaskは、brewに標準で組み込まれるようになったそうです。

```
$ brew install homebrew/cask/jad
```

#### ターミナルでJDKを使えるようにする
`d2j-dex2jar`を使うにあたりJDKをインストールする必要があるようです。ただ、今回はJDKをOracleのサイトからインストールするのではなく、Android studioで使っているJDKをターミナルで使えるようにするために、PATHを通しました。

```
$ echo 'export PATH=$PATH:/Applications/"Android Studio.app"/Contents/jre/jdk/Contents/Home/bin' >> ~/.bash_profile
$ echo 'export JAVA_HOME=/Applications/"Android Studio.app"/Contents/jre/jdk/Contents/Home' >> ~/.bash_profile
$ source ~/.bash_profile
```

#### デコンパイル

apkファイルを展開します。

```
$ unzip app-debug.apk
```

`d2j-dex2jar`を使って生成された`classes.dex`を`.jar`に変換します。

```
$ d2j-dex2jar classes.dex
```

`classes-dex2jar.jar`が生成されるので、展開します。

```
$ unzip classes-dex2jar.jar
```

（もともと難読化されているのかを確認していたので、気づかなかったのですが、このあたりで難読化設定を入れておくと`MainActivity.class`が見つかるのですが、入れてないと見つからないという不思議な状況になっていました。）
とりあえず続きを進めてみます。

`jad`を使って`.class`を`.java`にデコンパイルを行います。

```
$ jad hoge.class
```

この手順で、本来はデコンパイルが完了するはずです。


### 参考文献

+ https://github.com/skylot/jadx
+ https://qiita.com/chibi929/items/940623d33d9f6eb3877f
+ https://support.apple.com/ja-jp/HT208436