Android apk の解析

# 本投稿のねらい
* 比較的容易と言われている Android アプリを解析してみること
* 今回は、難読化ツールである ProGuard を使用していない apk を対象としている
    * ProGuard 適用時との比較は少し記載

# 対象とするソースコード
* https://github.com/kasaharu/AnalysisTestingApplication
* ビルドをして apk を作成：AnalysisTestingApplication.apk

# 拡張子 apk は zip に変換して解凍可能
* apk ファイルは zip 形式であるため、拡張子をリネームするだけで、解凍が可能になる。
* AnalysisTestingApplication.apk → AnalysisTestingApplication.zip
* zip ファイルを解凍したディレクトリ構成は以下のようになっている

```ディレクトリ
'.
├── AndroidManifest.xml
├── META-INF
│   ├── CERT.RSA
│   ├── CERT.SF
│   └── MANIFEST.MF
├── classes.dex
├── res
│   ├── drawable-hdpi
│   │   └── ic_launcher.png
│   ├── drawable-mdpi
│   │   └── ic_launcher.png
│   ├── drawable-xhdpi
│   │   └── ic_launcher.png
│   ├── drawable-xxhdpi
│   │   └── ic_launcher.png
│   ├── layout
│   │   ├── activity_main.xml
│   │   └── activity_next.xml
│   └── menu
│       └── main.xml
└── resources.arsc

8 directories, 13 files'
```
* AndroidManifest.xml などが確認できるが、実際はバイナリ化されており、そのままでの解釈は困難。
* リソースファイルはそのまま確認できる形式で入っているため、画像などでも大事なファイルはサーバからダウンロードなどの手法を使用するほうが望ましい

# 逆コンパイラを用いた解析の手順
* 使用するツール一覧
    * dex2jar : dex ファイルを jar ファイルに変換できる
        * https://code.google.com/p/dex2jar/ からダウンロード
            * dex2jar-0.0.9.15.zip
    * Java Decompiler : java ファイルへのデコンパイルができる
        * http://varaneckas.com/jad/ (公式サイトになかったため)
            * jad158g.mac.intel.zip

* apk を jar ファイルへ変換する

```bash
$ dex2jar.sh AnalisisTestingApplication.apk
```

* 以下の表示が出力されれば成功となる

```
this cmd is deprecated, use the d2j-dex2jar if possible
dex2jar version: translator-0.0.9.15
dex2jar AnalisisTestingApplication.apk -> AnalisisTestingApplication_dex2jar.jar
Done.
```
* AnalisisTestingApplication_dex2jar.jar が作成されることを確認。
* 作成された jar ファイルを下記のコマンドで class ファイル群に変換する

```bash
$ unzip AnalisisTestingApplication_dex2jar.jar -d ./Classes
```

* 次の構成でディレクトリが作成される

```
.
├── android
│   └── support
│       └── v4
│           ├── accessibilityservice
│           ├── app
│           ├── content
│           │   └── pm
│           ├── database
│           ├── graphics
│           │   └── drawable
│           ├── hardware
│           │   └── display
│           ├── internal
│           │   └── view
│           ├── media
│           ├── net
│           ├── os
│           ├── text
│           ├── util
│           ├── view
│           │   └── accessibility
│           └── widget
└── com
    └── kasaharu
        └── analysistestingapplication

25 directories
```

* jad を使用して class ファイルを java ファイルに変換する
    * 今回は、MainActivity.class を java ファイルに変換することとする

```bash
$ jad -p MainActivity.class > MainActivity.java
```


# 変換してできたファイルの確認
* 変換後

```java
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 

package com.kasaharu.analysistestingapplication;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.util.Log;
import android.view.*;
import android.widget.*;

// Referenced classes of package com.kasaharu.analysistestingapplication:
//            NextActivity

public class MainActivity extends Activity
{

    public MainActivity()
    {
    }

    private boolean IsCorrectPassWord()
    {
        String s = mInputPassWord.getText().toString();
        Log.d("forDebug", (new StringBuilder("inputText = ")).append(s).toString());
        return s.equals("test");
    }

    protected void onCreate(Bundle bundle)
    {
        super.onCreate(bundle);
        setContentView(0x7f030000);
    }

    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(0x7f070000, menu);
        return true;
    }

    public void onStart()
    {
        super.onStart();
        mNextButton = (Button)findViewById(0x7f080002);
        mInputPassWord = (EditText)findViewById(0x7f080001);
        mNextButton.setOnClickListener(new android.view.View.OnClickListener() {

            public void onClick(View view)
            {
                if(IsCorrectPassWord())
                {
                    Intent intent = new Intent(MainActivity.this, com/kasaharu/analysistestingapplication/NextActivity);
                    startActivity(intent);
                    return;
                } else
                {
                    Toast toast = Toast.makeText(MainActivity.this, "\u30D1\u30B9\u30EF\u30FC\u30C9\u304C\u9593\u9055\u3063\u3066\u3044\u307E\u3059\u3002", 0);
                    toast.setGravity(17, 0, 0);
                    toast.show();
                    return;
                }
            }

            final MainActivity this$0;

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

    private final String CORRECTPASS = "test";
    EditText mInputPassWord;
    Button mNextButton;

}
```

* オリジナルファイルとの比較

```java
package com.kasaharu.analysistestingapplication;

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {
	Button mNextButton;
	EditText mInputPassWord;

	private final String CORRECTPASS = "test";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public void onStart() {
		super.onStart();
		mNextButton = (Button)findViewById(R.id.btnNext);
		mInputPassWord = (EditText)findViewById(R.id.editTxtPW);
		mNextButton.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				boolean isCorrectPW = false;
				isCorrectPW = IsCorrectPassWord();
				if (isCorrectPW) {
    				Intent intent = new Intent(MainActivity.this, NextActivity.class);
    				startActivity(intent);
				} else {
			        Toast toast = Toast.makeText(MainActivity.this, "パスワードが間違っています。", Toast.LENGTH_SHORT);
			        toast.setGravity(Gravity.CENTER, 0, 0);
			        toast.show();
				}
			}
		});
	}

	private boolean IsCorrectPassWord() {
		String inputText = mInputPassWord.getText().toString();
		Log.d("forDebug", "inputText = " + inputText);
		if (inputText.equals(CORRECTPASS)) {
			return true;
		} else {
    		return false;
		}
	}
}
```

* 関数名を含めほとんど元通りに復元できていることがわかる

# 補足(ProGuard を使用して作成された apk を解析した場合)
* ProGuard を使用して作成された apk を解析した場合どのようになるかを確認
    * ProGuard の適用方法については割愛

```java
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 

package com.kasaharu.analysistestingapplication;

import android.app.Activity;
import android.os.Bundle;
import android.text.Editable;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.widget.Button;
import android.widget.EditText;

// Referenced classes of package com.kasaharu.analysistestingapplication:
//            a

public class MainActivity extends Activity
{

    public MainActivity()
    {
    }

    private boolean a()
    {
        String s = b.getText().toString();
        Log.d("forDebug", (new StringBuilder("inputText = ")).append(s).toString());
        return s.equals("test");
    }

    static boolean a(MainActivity mainactivity)
    {
        return mainactivity.a();
    }

    protected void onCreate(Bundle bundle)
    {
        super.onCreate(bundle);
        setContentView(0x7f030000);
    }

    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(0x7f070000, menu);
        return true;
    }

    public void onStart()
    {
        super.onStart();
        a = (Button)findViewById(0x7f080002);
        b = (EditText)findViewById(0x7f080001);
        a.setOnClickListener(new a(this));
    }

    Button a;
    EditText b;
    private final String c = "test";
}
```

* ProGuard を使用し難読化した場合、メソッド名等が解析されなくなる

# まとめ
* 難読化対策を全くしていない apk に対する逆コンパイルは非常に簡単
* 少しでも解析の難易度向上のために難読化をすることが望まれる