LoginSignup
46
37

More than 3 years have passed since last update.

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

Posted at

はじめに

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

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

デコンパイル図.png

環境・バージョン

  • macOS Catalina バージョン 10.15.6

失敗した方法

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

dex2jarやファイルを展開する手順を踏むことで、.classファイルが生成されます。
手順詳細はこちらへ。

その後、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

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

実行結果

元のコード(.kt)


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

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

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));
    }
}
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!");
    }
}
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環境がなかったので、試せていません。)

失敗した方法(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

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

参考文献

46
37
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
37