本投稿のねらい
- 比較的容易と言われている 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
-
https://code.google.com/p/dex2jar/ からダウンロード
- Java Decompiler : java ファイルへのデコンパイルができる
-
http://varaneckas.com/jad/ (公式サイトになかったため)
- jad158g.mac.intel.zip
-
http://varaneckas.com/jad/ (公式サイトになかったため)
- dex2jar : dex ファイルを jar ファイルに変換できる
-
apk を jar ファイルへ変換する
$ 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 ファイル群に変換する
$ 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 ファイルに変換することとする
$ jad -p MainActivity.class > MainActivity.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;
}
- オリジナルファイルとの比較
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 の適用方法については割愛
// 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 に対する逆コンパイルは非常に簡単
- 少しでも解析の難易度向上のために難読化をすることが望まれる