DroidKaigi2019のラック社の北原さんによる「FridaによるAndroidアプリの動的解析とフッキングの基礎」のセッションでは、OWASPのCrackmesアプリをフッキングする例を紹介されていました。
DroidKaigi 2019 - Frida による Android アプリの動的解析とフッキングの基礎 / Ken Kitahara [JA]
OWASP / UnCrackable Mobile Apps
対象のメソッドが特定できれば様々な改竄が可能とのことで、本記事ではサービス開発でよく使われるOkHttp3をフッキングすることで、対象のアプリの通信ログをコンソール上に出力する例を試してみたいと思います。
AndroidではLogCatに出力されるログは同じ端末の他のアプリからも読み取りが可能であることから、センシティブな情報を含めないことはもちろん、攻撃者に情報を与えないために、リリースバイナリではログが出力されないようにする、Proguard等でコード自体も自動削除されるようにするのが一般的です。通信に関しても同様で、OkHttp3はHttpLoggingInterceptor
という通信ログをLogcat上に出力するクラスがありますが、リリースバイナリではHttpLoggingInterceptor.Level.NONE
を指定するなどして出力されないようにするかと思います。
Androidアプリのセキュア設計・セキュアコーディングガイド / 4.8. LogCatにログ出力する
OWASP-MASVS V7,4
しかし、フッキングによってメソッド自体が改ざんされてしまうと、リリースバイナリでも容易に情報の取得が可能となります。
インストール
fridaをインストールします。
https://www.frida.re/docs/installation/
pip3 install frida-tools
実行にあたっては、対象の端末上でfrida-serverを起動しておく必要があります。
以下から実行する端末にあったfrida-serverのリリースバイナリをダウンロードして、完了したら端末にpushします。
https://github.com/frida/frida/releases
adb push frida-server-12.4.0-android-x86_64 /data/local/tmp/frida-server
実行権限を追加して、
adb shell ”chmod 755 /data/local/tmp/frida-server”
frida-serverを起動します。
adb shell "/data/local/tmp/frida-server &"
Unable to load SELinux policy from the kernel: Failed to open file “/sys/fs/selinux/policy”: Permission denied
のように権限でエラーが発生する場合はadb root
でroot権限での実行が必要です。
改ざん用のスクリプトを記述
まずはOkHttpClientのnewCall
メソッドを改ざんしてリクエストの内容を取得してみます。対象のOkHttpClientのコードは以下のようになっていて、引数でRequestが渡ってきますので、この内容を確認できれば目的が達成できます。
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
JavaScriptで改ざん用のスクリプトを記述します。
newCall
で渡ってくるRequestを受け取り、frida上のコンソールへログを出力し、元の実行結果を返り値として返すスクリプトを記述します。
詳しくはJavaScriptAPIのドキュメントを見ていただければと思いますが、
Java.use
で対象とするクラス(今回はOkHttpClient
)のインスタンスを取得し、そのクラスのもつメソッド(今回はnewCall
)に対してimplementation
で変更したい処理を与えることで、処理内容を改竄することが可能です。
Java.perform(function() {
var okHttpClient = Java.use("okhttp3.OkHttpClient");
okHttpClient.newCall.implementation = function (request) {
call = this.newCall(request)
console.log(request.toString());
return call;
};
});
実行
実行したいアプリのパッケージ名が必要なので、パッケージ名を調べます。
adb shell pm list package
frida-serverが起動している状態で、記述したスクリプトと対象のアプリのパッケージを指定して実行します。
frida -U -l bypass.js -f 'target.app.package'
fridaコンソール上が立ち上がったら、%resume
コマンドを実行することで、メインスレッドが開始されます。
一般のアプリをフッキングするのはまずいので、以下では例としてGoogleが公開しているアーキテクチャコンポーネントサンプルのPagingWithNetworkSampleに上記のスクリプトを適用してみます。このアプリではOkHttpが使われているので、リクエストした際にfridaコンソール上にリクエストログが出力されているのがわかるかと思います。
https://github.com/googlesamples/android-architecture-components/tree/master/PagingWithNetworkSample
その他、リクエストヘッダや、HTTPメソッドなどOkHttpのメソッドを指定して取得したい情報を出力することができます。ヘッダにトークン情報などが含まれている場合などは、このようにして出力が可能です。
console.log(request.headers().toString());
console.log(request.method().toString());
もちろんですが、同様の方法でレスポンスも出力が可能です。
Java.perform(function() {
var realCall = Java.use("okhttp3.RealCall");
realCall.getResponseWithInterceptorChain.implementation = function() {
response = this.getResponseWithInterceptorChain();
console.log(response.toString());
return response;
};
});
まとめ
OSSライブラリに関してはデコンパイル不要でクラスやメソッドが特定可能であり、アプリ自体を難読化していたとしても回避が難しいため、センシティブな情報を取扱う際には、そもそもアプリ上にある情報は取得される前提のサービス設計にすることや、開発者が容易に取得されることを理解して実装していく必要があります。
keystoreなどで暗号化して保存などを行なっていても、暗号化ロジック周りをフッキングされると取得できてしまうので、クライアント側でセンシティブな情報を安全に取り扱うのは困難です。
デバッグログを出力しないなどは他のアプリ経由で読み取られないようにという観点からするともちろん重要ですが、アプリの情報を隠す上ではあまり効果を発揮しないので、そのことも認識しておく方が良いと思いました。