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

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

# 手順

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

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

# 必要なツール

* dex2jar(手順2で使用する)
    * https://github.com/pxb1988/dex2jar
* Java Decompiler(手順4で使用する)
    * http://varaneckas.com/jad

# 逆コンパイルする

まずは、サンプル用のapkを用意します。
せっかくなので以下の記事で使用したサンプルアプリのapkを逆コンパイルしてみようと思います。
[ActivityとFragmentのライフサイクルと罠](http://qiita.com/shouta_a/items/78f0d3aa2ab4a0229978)

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

プロジェクト構成はこんな感じです。
![20150930220520.png](https://qiita-image-store.s3.amazonaws.com/0/53393/6bf5e317-1a90-c1d6-da5a-928b5c172e9c.png)

## 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ファイル` を見てみます。

```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);
            }
        });
    }
}
```

```java:逆コンパイル後の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文になっていたりします。

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