Posted at

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

More than 3 years have passed since last update.

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文になっていたりします。

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