Androidアプリのセキュリティ対策技術について解説する本連載。第5回は、広告目的で利用者の情報を取得しようとするアプリの挙動を監視する技術を紹介する。
Androidアプリをめぐるセキュリティ対策技術の最新動向を紹介する本連載。第4回では、「アプリがマルウェアでないか」「アプリに脆弱(ぜいじゃく)性が存在しないか」を判定する技術について解説した。第5回となる今回は、特に「広告目的」で利用者に関する情報を取得しようと試みるライブラリの挙動を監視し、アプリ利用者の安全を確保するための技術を紹介する。
Androidアプリには無料で配布されているものが多数あり、利用者にとっては大きなメリットとなっているが、そのようなアプリは画面に広告が表示されることが多い。この広告表示は、広告事業者によって提供される広告ライブラリモジュールを、アプリ開発者がアプリに組み込むことで実現されている。アプリに広告を組み込むことにより、開発者が広告事業者を通じて収入を得られるようになっている。
広告ライブラリモジュールは、広告効果を向上させるために、「ターゲティング広告」と呼ばれる利用者の嗜好(しこう)に合わせた広告を表示する仕組みを備えている。この仕組みの裏では、何らかの個人識別IDをキーとして、利用者が過去にクリックした広告の種類や、購入した商品などの情報を管理することで、利用者の嗜好に関する情報を蓄積し、活用している。このとき、広告ライブラリモジュールがどのような個人識別IDを用いるかが問題となる。
特に問題視されているのが、「グローバルID」と呼ばれる識別IDである。グローバルIDは、スマートフォンやSIMカードに割り当てられる番号など、ユーザーが自由に変更できないか、変更しづらいものを指す。具体的には、以下のようなものがグローバルIDに該当する。
グローバルIDはユーザーが容易に変更できないため、ユーザーは一度収集された情報を変更・削除することができない。すなわち、ユーザーの意思とは無関係に、広告事業者に情報を継続的に蓄積されてしまう。また、スマートフォンを譲渡すると、元の持ち主の趣味嗜好が分かってしまうといった問題も考えられる。さらに、異なる複数の広告事業者が収集しているデータであっても、グローバルIDは同一である可能性が高いため、データを結合すれば、個人の行動や趣味嗜好を抽出することも可能になる。
グーグルはこのような背景から2014年8月1日以降、広告のための追跡に、利用者が適宜リセット可能な広告ID(Advertising ID)を利用するよう定めている。この決定は極めて有意義だが、個々のアプリがこの規定を順守しているかどうか検査をしなければ有名無実化してしまう可能性もあるため、引き続き注視していく必要がある。
前述のようなグローバルIDの利用を検査するには、「アプリがグローバルIDを取得したかどうか」「取得したグローバルIDを送信したかどうか」などを監視する必要がある。アプリの監視の方法には、第2回で紹介した静的解析と動的解析があるが、このうち特に動的解析では、ソフトウェア階層(下図)のどの階層で観測を行うかが、大きな技術的選択の1つとなる。
この判断における指標には、以下のものがある。
上図で示した階層のうち、アプリの監視が網羅的に可能なのは、「アプリケーションフレームワーク」または「Linuxカーネル」である。しかし、前述のような広告ライブラリモジュールの監視においては、グローバルIDを取得するようなAndroid特有のAPI呼び出しを検出し、かつアプリ内のどのモジュールがその呼び出しをしたのかを追跡する必要があるため、アプリケーションフレームワークレベルでの監視が適している。Linuxカーネルは、ライブラリ以上のソフトウェア階層を1つのものとして捉えており、API呼び出しを補足することが難しいからだ。また、Linuxカーネルでは、どのソフトウェア階層の動作であるのかを区別することも難しい。すなわち、処理内容を推測しづらいのである。
広告ライブラリモジュールの監視においては、グローバルIDや、その他のセンシティブ情報を取得しようとする挙動を検出する必要がある。具体的には、次のようなAPIが対象となる。
Android ID | android.provider.Settings.Secure.getString() |
---|---|
IMEI | android.telephony.TelephonyManager.getDeviceId() |
IMSI | android.telephony.TelephonyManager.getSubscriberId() |
ICCID | android.telephony.TelephonyManager.getSimSerialNumber() |
電話番号 | android.telephony.TelephonyManager.getLine1Number() |
MACアドレス | android.net.wi.WiInfo.getMacAddress() |
advertising ID | android.content.Intent.setPackage() |
共有コンテンツ | android.content.Content.ContentResolver.query() |
アカウント情報 | com.android.server.accounts.AccountManagerService.getAccountsAsUser() |
インストール済みのアプリ一覧 | android.app.PackageManagerService.getInstalledApplications() |
電話発信ログ | android.provider.CallLog.Calls.getLastOutgoingCall() |
緯度 | android.location.Location.getLatitude() |
経度 | android.location.Location.getLongitude() |
Geolocation | com.android.webview.chromium.ContentSettingsAdapter.setGeolocationEnabled() |
音声入力 | android.content.Intent.getStringExtra() |
また、取得したこれらの情報を外部に送信しようとする動作についても検出する必要がある。従って、次のようなAPIも対象となる。
HTTP | java.net.URL.URLConnection.openConnection() |
---|---|
〃 | com.squareup.okhttp.internal.http.HttpURLConnectionImpl.connection() |
〃 | org.apache.http.impl.client.DefaultRequestDirector.execute() |
Socket | libcore.io.Posix.sendto() |
SSL | com.android.org.conscrypt.OpenSSLSocketImpl.SSLOutputStream.write() |
WebView | android.webkit.WebView.loadUrl() |
〃 | android.webkit.WebView.postUrl() |
〃 | android.webkitWebView.loadDataWithBaseURL() |
WebViewのredirect | WebViewContentsClientAdapter.shouldInterceptRequest() |
〃 | Intent android.app.Activity.startActivityForResult() |
〃 | android.app.Activity.startActivityAsUse() |
〃 | android.app.Activity.startActivityFromFragment() |
〃 | android.app.Activity.startActivities() |
さらに、Androidアプリ内でのスレッド生成や、メッセージ通信を利用した処理の依頼など、アプリの処理の流れを追跡するために、次のようなAPIも対象となる。
スレッド生成 | java.lang.Thread.start() |
---|---|
スレッドプール | java.lang.ThreadPoolExecute.execute() |
Handler | android.os.MessageQueue.enqueue() |
〃 | android.os.Looper.loop() |
Intent | android.app.Activity.startActivityForResult() |
〃 | android.app.Activity.startActivityAsUse() |
〃 | android.app.Activity.startActivityFromFragment() |
〃 | android.app.Activity.startActivities() |
〃 | android.content.ContextWrapper.startService() |
〃 | android.content.ContextWrapper.startServiceAsUser() |
addJavascriptInterface | android.webkit.WebView.addJavascriptInterface() |
ライブラリ読込み | java.lang.Runtime.loadLibrary() |
コマンド実行 | java.lang.ProcessManager.exec() |
ファイルアクセス | libcore.io.IoBridge.open() |
Copyright © ITmedia, Inc. All Rights Reserved.