Androidアプリ(DEXファイル)の改ざんとその対策について

コラム概要

■こんな方におすすめ:

  • JavaやKotlinを使用してAndroidアプリを作成する
  • Androidアプリの改ざん対策を知りたい

■難易度:★☆☆ ~ ★★☆

■ポイント:

  • JavaやKotlinファイルをAndroid環境で実行可能な形式に変換したものがDEXファイル。
  • DEXファイルは改ざんすることができてしまう。
  • DEX改ざんに対しては様々な方法により対策が可能。
  • 改ざんされる可能性を踏まえ、アプリの開発に取り組むことが重要である。

1.はじめに

Androidアプリを作成する際に、JavaやKotlinを用いる方は多いかと思います。
しかし、JavaやKotlinで記述したコードが
Android環境で動作する仕組みについて把握している方は少ないと思います。
さらに、実はJavaやKotlinで記述したコードを改ざんして動作させることまで可能なのです。

そこで、本コラムでは、以下3点について解説したいと思います。

・JavaやKotlinで記述したコードがAndroid環境で動作する仕組み
・JavaやKotlinで記述したコードを改ざんする手法
・JavaやKotlinで記述したコードを改ざんされないようにする対策

2.JavaやKotlinで記述したコードが
 Android環境で動作する仕組み

JavaやKotlinで記述したコードがAndroid環境で動作する仕組み、それは、
Androidアプリビルド時、JavaファイルやKotlinファイルを
Android環境で実行可能な形式であるDEX(Dalvik Executable/Dalvik実行可能)ファイルに変換しているから
です。

Androidアプリをビルドする際、まずJavaファイルやKotlinファイルはclassファイルとしてコンパイルされます。
ただしclassファイルはAndroid環境で実行できないため、さらに変換処理を行い、DEXファイルを作成します。

図1: JavaファイルをDEXファイルに変換するまでの簡易イメージ

Androidアプリの形式であるapkファイルや.aabファイルはいずれもzipフォーマットとなっています。
zip解凍ソフトを使って中身を展開すると、classes*.dexというファイルを確認することができますが、
これがDEXファイルです。

図2: Androidアプリ(.apk)をzipで解凍した場合のディレクトリ、classes.dexが確認できる

ちなみに、Javaはその言語仕様からCやC++と比較した場合に
逆コンパイル(バイナリデータからのソースコード生成)が容易であるという特徴があり、
実際にJava逆コンパイラは多くリリースされています。
そのため、C++ファイルをコンパイルしたSOファイルよりも、
JavaファイルやKotlinファイルをコンパイルすることで生成されるDEXファイルの方が
逆コンパイルの容易なファイルとなっています。

そのため、攻撃者にとっては、DEXファイルは中身の解析や改ざんが容易な狙いやすいファイルなのです。

DEXファイルについて理解が進んだところで、
続いてDEXファイルはどのように改ざんされるのか説明したいと思います。

3.DEXファイルの改ざん方法

まず、DEXファイルの改ざん方法を説明する前にサンプルアプリを用意しました。

サンプルアプリのMainActivityのjavaコード:

public class MainActivity extends AppCompatActivity {

    static int clickCount = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.button);
        TextView textView = findViewById(R.id.textView);

        button.setOnClickListener( v -> {
            clickCount = plusClickCount(clickCount);
            textView.setText(format("Count = %d", clickCount));
        });
    }

    int plusClickCount(int clickCount) {
        return clickCount + 1;
    }
}

サンプルアプリでは、ボタンをタップすると
plusClickCountという関数が呼び出されクリック回数が1追加されます。

動画1: サンプルアプリを動作させた動画

このサンプルアプリに対して、
1クリック当たりのカウント数を増加させるチートを行いたい攻撃者を想定します。

改ざん方法には様々なものがありますが、本コラムではApktoolというツールを使用します。
(インストール方法については公式サイトを参照ください。)

①apkファイルを展開
 apktool d [改ざんしたいアプリ]をコマンドプロンプトとして実行することで、アプリが展開されます。
 アプリの展開に伴い、DEXファイルはsmaliファイルという編集可能なファイル群に変換されます。

②smaliファイル群からカウント数を増やすような関数を検索
 目的の関数を検索する方法には様々なものがありますが、
 今回は関数名から目的の関数を検索する方法を使用します。
 例えば、ClickCountという文字列で検索すると、
 MainActivity内にplusClickCountという関数があることがわかりました。
 この関数は、今回カウント数を増やす関数です。

③カウント数を増やす関数を改ざん
 smaliファイルにおけるplusClickCountは以下のようになっています。

MainActivity.smaliよりplusClickCount関数のみ抜粋

.method plusClickCount(I)I
    .locals 0

    add-int/lit8 p1, p1, 0x1

    return p1
.end method

 add-int/lit8 p1, p1, 0x1はp1 = p1 + 0x1という意味になります。
 この関数を実行することで1クリックにつきカウント回数が1増えることは
 アプリを動作させた際に確認したため、
 0x1を0xaに書き換えることで、1クリックにつきカウント回数を10に増やすことができます。

④apkファイルを再構築
 apktool b [展開したアプリのフォルダ] -o [再構築後のアプリ名]を実行することで、
 ③の改ざんを適用したアプリが再構築されます。

⑤apkファイルを署名
 ④にて再構築したアプリは署名されていないため、署名します。

以上の工程により改ざんアプリを作成することができました。
このアプリをインストールして動作すると……

動画2: 改ざんしたアプリを動作させた動画

1回クリックするごとにカウントが10増えるようになりました。

このようにDEXファイルを改ざんすることができることがわかりました。

4.DEXファイルの改ざん対策

DEXファイルの改ざんができることがわかりましたが、アプリ開発者はどのように対策を打つべきでしょうか。
対策には様々なものが考えられますが、本コラムでは3点紹介します。

①シンボルを難読化

アプリモジュールのbuild.gradleにminifyEnabled = trueを設定することにより、
シンボルを難読化することが可能です。
これにより、特定の関数を除くメソッド名が難読化されます。

難読化前難読化後              
com.app.sample.SampleClass sampleMethodd.g i
表1: 難読化のイメージ

また、単純なコードの場合はメソッド名の難読化ではなく他関数との統合が行われる場合もあります。

シンボルを難読化されたアプリを同様に改ざんしようとしてみますが、
plusClickCountというメソッドは
メソッド名の難読化あるいは他関数への統合が行われることにより、存在しなくなりました。
よって、攻撃者はメソッド名からどのような関数か想定し改ざんする方法が
使用できなくなったため、改ざん難易度が上がります。

②コードの難読化

続いて、コード自体の難読化を紹介します。
今回はreturn clickCount + 1;というわかりやすいロジックのみを実行する関数を実行していたため、
改ざん箇所の判断が容易でした。
そこで、コードを難読化することで、
どの箇所を改ざんすれば目的の改ざんが成功するかわからないようにすることができます。
コードの難読化手法については、当社のコラムにて紹介していますので、そちらもご覧ください。

③署名チェック機能

アプリを再構築すると何らかの署名鍵を用いて再度アプリを署名する必要があります。

しかし、アプリの開発者でない限りは、
アプリビルド時の署名とは異なる署名鍵を使用して署名をすることになります。
それを利用して、アプリビルド時の署名情報と現在の署名情報を比較することで改ざんを防止できます。

今回はpackageManager.hasSigningCertificateという関数を紹介します。
まず、適当なフォルダにアプリビルド時の署名のフィンガープリントを記載したファイルを配置しておきます。

Assetsフォルダに配置したsignature.txtのイメージ
0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
(このtxtに記載した文字列は、コマンドプロンプトでkeytool -v -list -keystore [keystoreファイル]を実行し、
 「証明書のフィンガープリント」の項目に記載されているSHA256のフィンガープリントです。
 コロン(“:”)は不要なため削除した上で記載してください。)

アプリ起動時にそのファイルからフィンガープリントを取得し、

packageManager.hasSigningCertificate([パッケージ名], [取得したフィンガープリント], PackageManager.CERT_INPUT_SHA256);

を実行します。
関数の返り値がtrueなら現在の署名はアプリビルド時の署名と同一であるため、改ざんされていません。
関数の返り値がfalseなら現在の署名はアプリビルド時の署名と異なるため、改ざんされています。
falseの場合強制的にアプリを終了させるなどすることで、改ざんされたアプリの動作を防止します。

署名チェック関数の実装イメージ:

public static boolean checkSignature(Context context) {
   boolean signatureOK = false;
   InputStream inputStream = null;
   BufferedReader bufferedReader = null;

   try {
      inputStream = context.getAssets().open("signature.txt");
      bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

      String tempHexStr = bufferedReader.readLine();
      byte[] signature = Hex.decodeHex(tempHexStr.toCharArray());
      PackageManager packageManager = context.getPackageManager();
      signatureOK = packageManager.hasSigningCertificate(
         "com.app.sample",
         signature,
         PackageManager.CERT_INPUT_SHA256
      );
      if (inputStream != null) inputStream.close();
      if (bufferedReader != null) bufferedReader.close();
   } catch (Exception e) {
      e.printStackTrace();
   }
   return signatureOK;
}

ここまで3点紹介しましたが、1つ1つは破られる可能性がある対策です。
しかし、これらの対策を併用する、あるいは他の対策を追加することで、改ざんの難易度を上げることが可能です。

5. 終わりに

いかがでしたでしょうか。
本コラムでは、DEX改ざんが可能なこと、DEX改ざん対策を取ることが可能なことを解説しました。
アプリ開発においては、アプリの改ざんは起こり得るということをまず知る必要があります。
その上で、どのような対策が可能であるかを検討しなければなりません。


弊社では、
DEXファイルを含むAndroidアプリへの
不正な解析・改ざん行為(クラッキング)を防ぐ対策製品
CrackProof for Android DEX」の開発・販売を行っております。

CrackProof for Android DEXのご紹介

CrackProofについて、詳しく話を聞いてみたいと思われた方は
当サイトからお気軽にお問い合わせください。

お問い合わせはこちら

6. 参考文献

Android Gradle プラグインのリリースノート:
https://developer.android.com/studio/releases/gradle-plugin

Dalvik 実行可能ファイルの形式:
https://source.android.com/docs/core/runtime/dex-format

Apktool公式サイト:
https://ibotpeaches.github.io/Apktool/

PackageManager.hasSigningCertificateについての公式マニュアル:
https://developer.android.com/reference/android/content/pm/PackageManager#hasSigningCertificate(java.lang.String,%20byte[],%20int)