AndroidのAPKを逆コンパイルする


2年ほど前に知ってて当たり前のように教えていただきました。
最近、再び逆コンパイル(デコンパイル)する機会がございましたので、Qiita記事としてメモを投稿しておこうと思います。

手順

  1. apkファイル から dexファイル を取り出す
  2. dexファイルjarファイル に変換する
  3. jarファイル から classファイル を取り出す
  4. classファイルjavaファイル に変換する

だいたいこんな感じです。

必要なツール

逆コンパイルする

まずは、サンプル用のapkを用意します。
せっかくなので以下の記事で使用したサンプルアプリのapkを逆コンパイルしてみようと思います。
ActivityとFragmentのライフサイクルと罠

GitHubへのリンクは↓ですね。
https://github.com/chibi929/AndroidLifeCycle

プロジェクト構成はこんな感じです。

apkファイル から dexファイル を取り出す

apkファイルはzipなので unzip で解凍ができます。

解凍前の状況がこちら

$ ls
app-debug.apk

unzipコマンドの様子がこちら

$ unzip app-debug.apk 
Archive:  app-debug.apk
  inflating: AndroidManifest.xml     
  inflating: res/anim/abc_fade_in.xml  
  ~略~

unzipで解凍後の状況がこちら

$ ls
AndroidManifest.xml  classes.dex  META-INF  res  resources.arsc

classes.dex が取り出せました。

dexファイル を jarファイル に変換する

ここで dex2jarの出番です。
今回は記事投稿時点で最新版だった dex2jar-2.0 を使います。

dex2jarのスクリプトファイルに実行権限がなかったので付けました。

$ chmod +x -R dex2jar-2.0

dex2jar実行の様子がこちら

$ ~/tool/dex2jar-2.0/d2j-dex2jar.sh classes.dex 
dex2jar classes.dex -> ./classes-dex2jar.jar

dex2jar実行後の状況がこちら

$ ls
AndroidManifest.xml  app-debug.apk  classes.dex  classes-dex2jar.jar  META-INF  res  resources.arsc

classes-dex2jar.jar が増えています。

jarファイル から classファイル を取り出す

jarファイルはご存知の通りzipですので、apkファイル同様unzipをします。

unzipコマンドの様子がこちら

$ unzip classes-dex2jar.jar 
Archive:  classes-dex2jar.jar
   creating: android/
   creating: android/support/
   ~略~

unzipで解凍後の状況がこちら

$ ls
android  AndroidManifest.xml  app-debug.apk  chibi  classes.dex  classes-dex2jar.jar  META-INF  res  resources.arsc

androidディレクトリchibiディレクトリ が生まれています。
chibiディレクトリ は以下のような構成になっております。

chibi/jp/lifecycle
│  AppActivity.class
│  AppFragment.class
│  BuildConfig.class
│  MainActivity$1.class
│  MainActivity$2.class
│  MainActivity.class
│  R$anim.class
│  R$attr.class
│  R$bool.class
│  R$color.class
│  R$dimen.class
│  R$drawable.class
│  R$id.class
│  R$integer.class
│  R$layout.class
│  R$menu.class
│  R$mipmap.class
│  R$string.class
│  R$style.class
│  R$styleable.class
│  R.class
│  V4AppActivity.class
│  V4AppFragment.class
│
└─base
        LifeCycleActivity.class
        LifeCycleFragment.class
        LifeCycleFragmentActivity.class
        LifeCycleV4Fragment.class

一番最初に載せたプロジェクト構成の画像とほぼ同じですね。

classファイル を javaファイル に変換する

最後に Java Decompiler の出番です。

$ ~/tool/jad -o -r -sjava -ddecomp chibi/**/*.class
Parsing chibi/jp/lifecycle/BuildConfig.class...The class file version is 50.0 (only 45.3, 46.0 and 47.0 are supported)
 Generating decomp/chibi/jp/lifecycle/BuildConfig.java
Parsing chibi/jp/lifecycle/R.class...The class file version is 50.0 (only 45.3, 46.0 and 47.0 are supported)
~略~

コマンドは Java Decompiler の Readme.txt に書いてあったものをそのまま・・・。
それでは decompディレクトリ のディレクトリ構成を確認します。

decomp
└─chibi
    └─jp
        └─lifecycle
            │  AppActivity.java
            │  AppFragment.java
            │  BuildConfig.java
            │  MainActivity.java
            │  R.java
            │  V4AppActivity.java
            │  V4AppFragment.java
            │
            └─base
                    LifeCycleActivity.java
                    LifeCycleFragment.java
                    LifeCycleFragmentActivity.java
                    LifeCycleV4Fragment.java

もう、ほぼ元通りです。

最後に

本物のjavaファイル逆コンパイル後のjavaファイル を見てみます。

本物のMainActivity.java
package chibi.jp.lifecycle;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.appFragmentButton).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent i = new Intent(getApplicationContext(), AppActivity.class);
                startActivity(i);
            }
        });

        findViewById(R.id.v4AppFragmentButton).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent i = new Intent(getApplicationContext(), V4AppActivity.class);
                startActivity(i);
            }
        });
    }
}
逆コンパイル後のMainActivity.java
// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3) 

package chibi.jp.lifecycle;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

// Referenced classes of package chibi.jp.lifecycle:
//            AppActivity, V4AppActivity

public class MainActivity extends AppCompatActivity
{

    public MainActivity()
    {
    }

    protected void onCreate(Bundle bundle)
    {
        super.onCreate(bundle);
        setContentView(0x7f04001a);
        findViewById(0x7f0c0050).setOnClickListener(new android.view.View.OnClickListener() {

            public void onClick(View view)
            {
                view = new Intent(getApplicationContext(), chibi/jp/lifecycle/AppActivity);
                startActivity(view);
            }

            final MainActivity this$0;


            {
                this$0 = MainActivity.this;
                super();
            }
        });
        findViewById(0x7f0c0051).setOnClickListener(new android.view.View.OnClickListener() {

            public void onClick(View view)
            {
                view = new Intent(getApplicationContext(), chibi/jp/lifecycle/V4AppActivity);
                startActivity(view);
            }

            final MainActivity this$0;


            {
                this$0 = MainActivity.this;
                super();
            }
        });
    }
}

MainActivity.javaにはif文が無かったのですが、
if文があると逆コンパイル後のjavaファイルにはgoto文になっていたりします。

以上。メモがてらの逆コンパイルでした。