Edited at

Android apk の解析

More than 5 years have passed since last update.


本投稿のねらい


  • 比較的容易と言われている Android アプリを解析してみること

  • 今回は、難読化ツールである ProGuard を使用していない apk を対象としている


    • ProGuard 適用時との比較は少し記載




対象とするソースコード


拡張子 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 ファイルに変換できる



    • Java Decompiler : java ファイルへのデコンパイルができる





  • 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 に対する逆コンパイルは非常に簡単

  • 少しでも解析の難易度向上のために難読化をすることが望まれる