Help us understand the problem. What is going on with this article?

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 に対する逆コンパイルは非常に簡単
  • 少しでも解析の難易度向上のために難読化をすることが望まれる
kasaharu
ふろんとえんどえんじにあ
https://kasaharu-web.web.app/
classi
学校の先生・生徒・保護者向けのB2B2Cの学習支援Webサービス「Classi(クラッシー)」 を開発・運営している会社です。
https://classi.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away