こんにちは。

今回はセキュアなapkの作り方について少しまとめます。apkってなんだ?という方は以下の記事をどうぞ。

そもそもこの記事を書こうと思ったのはJavaが原因なんですよね。

Recap

とりあえず3つの手法があるようです。

Isolate Java Program

重要なClassをサーバー側に置いておくことです。クライアントとは通信によってそのファイルへアクセスさせます。こうすることでデコンパイル自体を不可能することができます。重要なデータについてもサーバー側に置いておくことでデクリプションの可能性もなくなります。

Convert to Native Codes

アンドロイドアプリケーションはJava(Kotlin)が一般的です。しかしJavaはデコンパイルされやすいです。(理由は後述します。)そこでデコンパイルが難しいネイティブを組み込む方法です。ネイティブはデコンパイルしたところでアセンブリになります。画像をもって説明します。

NDKはアプリケーションの一部をネイティブに変換するためのツールです。また、コンパイルされたsoファイル(共有ファイル、windowsの場合は.dll)はJNIによってJavaが扱えるようにされます。また、ネイティブを使うことによって早くなることもメリットの一つになります。

一方でデメリットもあります。一つはファイルが大きくなることです。アンドロイドはさまざまなアーキテクチャ上で動くことができるため、その分互換性のあるファイルを多く持つ必要があります。デコンパイルしたapkのlib内を確認してみます。

上の画像からわかるようにそれぞれのマイクロプロセッサーに対応する機械語のファイルがあることがわかります。

Code Obfuscation

ProGuardなどのツールを用いてコードを難読化する方法です。クラス名やメソッド名が乱数になったりします。以下をしてくれます。

  1. Shrinking – detects and removes unused classes, fields, methods, and attributes.
  2. Optimization – analyzes and optimizes the bytecode of the methods.
  3. Obfuscation – renames the remaining classes, fields, and methods using short meaningless names.

具体的なObfuscationの方法はconfiguration file(build.gradle)に設定するようです。

詳しくはドキュメントを読んでください。

Why is Java easily cracked?

先ほど対策方法でネイティブを埋め込む理由としてJavaはデコンパイルされやすいと言いました。それについて少し深入りします。そもそもJavaのコンパイル、デコンパイルの流れを確認しましょう。

JavaのコードはDEXと呼ばれるバイトコードに変換されます。これはARTで動くためです。最近のアプリケーションはARTで実行されます。一昔前はJavaVMで動いていました。で、問題はこのバイトコードがデコンパイルされやすいことです。デコンパイルの過程も復習します。

復習ですがsmaliファイルはdexのアセンブリでした。このデコンパイルが簡単な理由は次のようです。

簡単に言えば、コンパイルの最適化が単純なことが原因です。メタデータ(クラス名やメソッド名)が付与されることが大きい理由らしいです。で、なぜコンパイルが単純なのか僕なりに調べました。原因はJITです。

Just-In-Time Compiler

JITはJavaのコンパイラです。ネイティブのコンパイルとの違いはタイミングです。これはdexをnative codeに変換します。公式からワークフローを拾ってきました。

よくわからないのでコピペで引用します。

JIT コンパイラは、ART の現行の事前(AOT)コンパイラを補完し、ランタイムのパフォーマンスの改善、ストレージ容量の節約、アプリとシステムのアップデートの高速化を実現します。また、アプリの自動アップデート中のシステム シャットダウンや無線(OTA)アップデート中のアプリの再コンパイルを回避することで、AOT コンパイラのパフォーマンスも改善します。

ここはまだ理解できる気がしないので今後の課題にしておきます。

ちなみに、dexじゃなくて機械語にコンパイルすればいいじゃんということにもなります。GCJというコンパイラが一応ありますが詳しくはわかっておりません。

Reverse Engineering Native Code

Javaが単純なのでNativeで書こう、ということになりました。ではNative Codeは本当に安全なのでしょうか?少しだけ深入りします。

僕の中の回答はIDA Proの商用版で解析することです。Free版だと対応のCPUがありません。。。。まあせっかくなのでJavaでnativeが動くまでについても調べてみます。

ネイティブメソッドが走るには.so内のSystem.loadLibraryかSystem.loadが呼ばれる必要があるようです。そしてその時に先ほど紹介したJNIのJNI_OnLoad()とかが使われるらしいです。じゃあ具体的にNativeってJavaの中でどう書かれているの?と思うのでみてみます。

ちょっと複雑ですが簡単にいうとnativeがあればネイティブメソッドということです。詳しくは参照よりどおぞ。

感想

やはりネイティブ最高、リバエン最高ですね

References