Android

Android apk の解析

More than 4 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 に対する逆コンパイルは非常に簡単
  • 少しでも解析の難易度向上のために難読化をすることが望まれる