アプリをエミュレートするアプリの登場とその危険性 / How multi-account app works #DroidKaigi

アプリをエミュレートするアプリの登場とその危険性 / How multi-account app works #DroidKaigi

ここ数年でいくつかの、他のAndroidアプリをエミュレートするAndroidアプリが登場しています(Parallel Space, GO Multipleなど)。その多くがLINEなどのメッセージングアプリやゲームなどを2アカウント同時使用できることを謳っており、多くのユーザーに使われていると思われます。
本セッションではこれらのアプリがどのようにして他のアプリをエミュレートしているのか調査した結果と、これらのアプリを使用することによる危険性の分析について発表を行います。

A87027204ff57be1dadbf36a78a73c1b?s=128

Takaki Hoshikawa

February 09, 2018
Tweet

Transcript

  1. #DroidKaigi_room5 アプリをエミュレートするアプリの登場と その危険性 2018/02/09 DroidKaigi 2018 エムスリー株式会社 星川貴樹 (@oboenikui)

  2. #DroidKaigi_room5 # 自己紹介 ## 名前 星川貴樹 Takaki Hoshikawa ## ニックネーム

    @oboenikui ## 会社 エムスリー株式会社(新卒1年目) 主にAndroidアプリ担当 ## 趣味・最近気になってること 野球観戦、CTF、minifyされたJSから隠し機能を見つけること 講演中に「OK Google」言ったらテロ起こせる説
  3. #DroidKaigi_room5 # 本日の内容 - アプリを「エミュレートする」アプリとは - マルチアカウントアプリの仕組み - マルチアカウントアプリは安全?危険? ※

    マルチアカウントアプリ ≒ アプリをエミュレートするアプリ
  4. #DroidKaigi_room5 # アプリを「エミュレートする」アプリとは - 一般には「マルチアカウントアプリ」として知られる - LINEやソシャゲなど、マルチアカウントの仕組みを持た ないアプリの別アカウントを作れることがウリ - 2016年ごろから急増

    マルチアカウントアプリの例 Parallel Space GO Multiple 2Accounts
  5. #DroidKaigi_room5 # マルチアカウントアプリの種類 ## APKを改竄してインストールするタイプ - アプリケーションIDを変更して自己署名するタイプ - 一般に証明書のチェックで弾ける -

    App Clonerなどが有名 ## 別のアプリとしてインストールせずに動かすタイプ - 「マルチアカウントアプリ」の中で動かすタイプ - あたかも正常にインストールされたかのように動かされ るため、動かされる側のアプリには判定が少し厳しい - 今回はこちらの話
  6. #DroidKaigi_room5 最初に抱いた感想…… これ、やばいアプリでは……

  7. #DroidKaigi_room5 最初に抱いた感想…… これ、やばいアプリでは…… ↓ 紐解いていきます

  8. #DroidKaigi_room5 # 用語 - マルチアカウントアプリ上で動くアプリのこと ➔ ゲストアプリ - マルチアカウントアプリのこと ➔

    ホストアプリ
  9. #DroidKaigi_room5 # 関連するものの本日深くは解説しないこと - Androidのシステムがどう動いているか - 普通のアプリがどうやって動いているか - APKファイルの構造 -

    Linuxの超基礎知識
  10. #DroidKaigi_room5 マルチアカウントアプリの仕組み

  11. #DroidKaigi_room5 # VirtualAppの紹介 - GitHub上でソースが公開されているマルチアカウントア プリ - VirtualAppは商用アプリのため、勝手にソースを組み 込むことを禁止している https://github.com/asLody/VirtualApp/

  12. #DroidKaigi_room5 # AndroidManifest.xmlから挙動を読み解く

  13. #DroidKaigi_room5 # AndroidManifest.xmlから挙動を読み解く uses-permissionの記述 (186個) StubActivityを継承したActivity (100個) StubContentProviderを継承した ContentProvider (50個)

  14. #DroidKaigi_room5 # AndroidManifest.xmlから挙動を読み解く ## AndroidManifestから読み解けること - パーミッションは考えうる全てのものをVirtualApp側 に記載 - システムの欠陥を突いて……とかではない

    - StubActivityは各アプリを動かしている……?
  15. #DroidKaigi_room5 # AndroidManifest.xmlから挙動を読み解く ## AndroidManifestから読み解けること - パーミッションは考えうる全てのものをVirtualApp側 に記載 - システムの欠陥を突いて……とかではない

    - StubActivityは各アプリを動かしている……? ↑ ※違います!! (後で解説)
  16. #DroidKaigi_room5 # ざっくりとした起動までの流れ 0. 動かしたいアプリのAPKファイルを読み込みインストール 1. ゲストアプリプロセスの起動 2. Activityを起動

  17. #DroidKaigi_room5 # ざっくりとした起動までの流れ 0. 動かしたいアプリのAPKファイルを読み込みインストール 1. ゲストアプリプロセスの起動 2. Activityを起動

  18. #DroidKaigi_room5 # アプリのインストール処理 - android.content.pm.PackageParser※を使ってパッ ケージ情報を取得 - /data/data/io.virtualapp/virtual/data以下 に、各アプリのデータ領域にあたるディレクトリなどを 作成

    - APKファイルからlib/**/*.soなどをコピー
  19. #DroidKaigi_room5 # PackageParserについて - AndroidManifest.xmlをパースするクラス - JavaDocで@hide指定された隠蔽クラスのため、SDKに は含まれず通常使用ができない /** ...

    * @hide */ public class PackageParser { ...
  20. #DroidKaigi_room5 # 隠蔽クラス/メソッドの使用 - @hide指定による隠蔽クラスやメソッドは、Javaのリフ レクションを用いるとアプリからもアクセス可能 - ただしAndroidバージョンごとに大幅に仕様変更がある ので通常使用すべきではない -

    解説するメソッドの多くは隠蔽メソッドなのでその度に 言及は特にしない
  21. #DroidKaigi_room5 # リフレクションとは - privateメソッドを実行する - privateなメンバ変数を取得・書換する - クラス名文字列から対応するクラスのインスタンスを生 成する(コンストラクタに引数を渡す場合)

    - メソッド実行前後に処理を挟む、実行結果を変える (Proxy) などが可能なJavaの機能(いわゆる黒魔術)
  22. #DroidKaigi_room5 # privateメンバ変数の取得 Field f = activity.getClass() .getDeclaredField("mMainThread"); f.setAccessible(true); ActivityThread

    mainThread = (ActivityThread) f.get(activity);
  23. #DroidKaigi_room5 # 名前からインスタンス生成 (LayoutInflaterの処理) Class<? extends View> clazz = context.getClassLoader()

    .loadClass(name) .asSubclass(View.class); Constructor<? extends View> constructor = clazz.getConstructor(constructorSignature); constructor.setAccessible(true); View view = constructor.newInstance(args);
  24. #DroidKaigi_room5 # Proxyの例 Map proxyInstance = (Map) Proxy.newProxyInstance( SomeTest.class.getClassLoader(), new

    Class[] { Map.class }, (proxy, method, args) -> { if (method.getName().equals("get")) return 42; throw new UnsupportOperationException(); }); proxyInstance.get(); // 42 proxyInstance.put(1, 1); // error
  25. #DroidKaigi_room5 # インストール後のdataディレクトリ /data/data/io.virtualapp/virtual/data内 - app/[application id]/ ∗ 各アプリのネイティブバイナリ・パッケージ キャッシュを保存

    - user/0/[application id] ∗ データディレクトリ (/data/data/[application id]) に相当
  26. #DroidKaigi_room5 # ざっくりとした起動までの流れ 0. 動かしたいアプリのAPKファイルを読み込みインストール 1. ゲストアプリプロセスの起動 2. Activityを起動

  27. #DroidKaigi_room5 # プロセスの状態 ホストアプリプロセス Managerプロセス ゲストアプリプロセス (ContentProvider) Activity起動要求 ゲスト起動処理 プロセス起動

    VMのロード & JNIをフック ActivityThread改竄 (Application作成)
  28. #DroidKaigi_room5 # プロセスの状態 ホストアプリプロセス Managerプロセス ゲストアプリプロセス (ContentProvider) Activity起動要求 ゲスト起動処理 プロセス起動

    VMのロード & JNIをフック ActivityThread改竄 (Application作成)
  29. #DroidKaigi_room5 # ContentProviderとは コンテンツ プロバイダは、データの中央リポジトリへのア クセスを管理します。(中略) これは、プロバイダと他のアプリケーションとの間のイン ターフェースになります。 https://developer.android.com/guide/topics/pr oviders/content-provider-creating.html?hl=ja

    - ContentUrisという仕組みでSQLiteのデータをやりと りしたりする - ContentProviderを含むプロセスはkillされにくい!
  30. #DroidKaigi_room5 # StubContentProviderの起動 - ContentProviderは別プロセスで動かせるため、この プロセスをアプリプロセスに見立てて動かす - ランタイムのセットアップに関する処理は StubContentProvider上で行う -

    ゲストアプリごとに別プロセスの StubContentProviderを起動する
  31. #DroidKaigi_room5 # プロセスの状態 ホストアプリプロセス Managerプロセス ゲストアプリプロセス (ContentProvider) Activity起動要求 ゲスト起動処理 プロセス起動

    VMのロード & JNIをフック ActivityThread改竄 (Application作成)
  32. #DroidKaigi_room5 # ART/DalvikVMをロードする - ネイティブ側でlibart.soもしくはlibdvm.soをロード (dlopen)する - dlopenにもパスを変更する処理が追加されている - JNIのフックについては後述

  33. #DroidKaigi_room5 # ART/DalvikVMをロードする (補足) - システムプロパティに persist.sys.dalvik.vm.lib.2が存在 → 通常はlibart.soのパスがセットされている persist.sys.dalvik.vm.libが存在

    → 通常はlibdvm.soのパスがセットされている
  34. #DroidKaigi_room5 # プロセスの状態 ホストアプリプロセス Managerプロセス ゲストアプリプロセス (ContentProvider) Activity起動要求 ゲスト起動処理 プロセス起動

    VMのロード & JNIをフック ActivityThread改竄 (Application作成)
  35. #DroidKaigi_room5 # ActivityThreadの略説 - アプリケーションを動かしてる一番重要な部分 - public static void main(String[]

    args)があるところ - ブレークポイントを貼るとコールスタックの初めの方に出てくる やつ - Looperが回ってるところ - HというHandlerを継承したクラスがあり、そこでライフ サイクルイベントやActivity起動などを処理している - 詳しくはAndroidを支える技術〈Ⅱ〉の5章を読んでね!
  36. #DroidKaigi_room5 # ActivityThreadの改竄 - ContentProviderの生成と同時にActivityThreadも 生成される - ゲストアプリを動かす際、ActivityThread内で持つ情 報をゲストのものに改竄する必要があるので改竄を行う -

    Proxyを使ったメソッドの処理改竄などもここで行う
  37. #DroidKaigi_room5 # ActivityThreadの改竄 # ActivityThread#mBoundApplication (AppBindData) の改竄 - ActivityThreadから取得し、情報を改竄 -

    appInfo, providers → PackageParserで取得 - processName → パッケージ名 - instrumentationName → パッケージ名などから生成
  38. #DroidKaigi_room5 # ActivityThreadの改竄 # AppBindData#info (LoadedApk) の生成 - Context#createPackageContext(packageName)を 用いて、対象のアプリのContextを取得

    - ContextImpl#mPackageInfo (LoadedApk) を取得
  39. #DroidKaigi_room5 # ActivityThreadの改竄 # Applicationの生成 - 生成したLoadedApkのインスタンスを用いて LoadedApk#makeApplication()を実行し生成 - ActivityThread#mInitialApplicationに作成した

    Applicationをセット - 生成後ライフサイクル処理を走らせる
  40. #DroidKaigi_room5 # ざっくりとした起動までの流れ 0. 動かしたいアプリのAPKファイルを読み込み、インストー ル 1. ゲストアプリプロセスの起動 2. Activityを起動

  41. #DroidKaigi_room5 # プロセスの状態 ホストアプリプロセス Managerプロセス ゲストアプリプロセス (ContentProvider) Activity起動要求 ゲスト起動処理 プロセス起動

    Context# startActivity Intent差し替え Intentから Activityを生成
  42. #DroidKaigi_room5 # プロセスの状態 ホストアプリプロセス Managerプロセス ゲストアプリプロセス (ContentProvider) Activity起動要求 ゲスト起動処理 プロセス起動

    Context# startActivity Intent差し替え Intentから Activityを生成
  43. #DroidKaigi_room5 # Activityの起動 - ManagerプロセスからstartActivity(intent)で StubActivityを起動しようとする - StubActivityとStubContentProviderは同じプロセスで動 くようAndroidManifestで定義されている -

    Androidの機能でゲストプロセス側にIntentが飛ぶ
  44. #DroidKaigi_room5 # プロセスの状態 ホストアプリプロセス Managerプロセス ゲストアプリプロセス (ContentProvider) Activity起動要求 ゲスト起動処理 プロセス起動

    Context# startActivity Intent差し替え Intentから Activityを生成 Intent Target: io.virtualapp/.StubActivity Extra: Intent(x.guest/.MainActivity)
  45. #DroidKaigi_room5 # プロセスの状態 ホストアプリプロセス Managerプロセス ゲストアプリプロセス (ContentProvider) Activity起動要求 ゲスト起動処理 プロセス起動

    Context# startActivity Intent差し替え Intentから Activityを生成
  46. #DroidKaigi_room5 # ActivityThreadの略説 - アプリケーションを動かしてる一番重要な部分 - public static void main(String[]

    args)があるところ - ブレークポイントを貼るとコールスタックの初めの方に出てくる やつ - Looperが回ってるところ - HというHandlerを継承したクラスがあり、そこでライフ サイクルイベントやActivity起動などを処理している - 詳しくはAndroidを支える技術〈Ⅱ〉の5章を読んでね!
  47. #DroidKaigi_room5 # Activityの起動 - ゲストアプリプロセスでIntentを受信後、 H#dispatchMessage(msg)が呼ばれる - 通常は受信したIntentからActivity生成処理が走る

  48. #DroidKaigi_room5 Handlerクラスの実装 public void dispatchMessage(Message msg) { if (msg.callback !=

    null) { handleCallback(msg); } else { if (mCallback != null && mCallback.handleMessage(msg)) { return; } handleMessage(msg); } }
  49. #DroidKaigi_room5 Handlerクラスの実装 public void dispatchMessage(Message msg) { if (msg.callback !=

    null) { handleCallback(msg); } else { if (mCallback != null && mCallback.handleMessage(msg)) { return; } handleMessage(msg); } } mCallbackがセットされていて、 実行の結果trueが返ってきたら終了
  50. #DroidKaigi_room5 Handlerクラスの実装 public void dispatchMessage(Message msg) { if (msg.callback !=

    null) { handleCallback(msg); } else { if (mCallback != null && mCallback.handleMessage(msg)) { return; } handleMessage(msg); } } Handlerのコンストラクタに渡す コールバックで、Hではnull Hでは通常ここの処理でActivity生成
  51. #DroidKaigi_room5 # プロセスの状態 ホストアプリプロセス Managerプロセス ゲストアプリプロセス (ContentProvider) Activity起動要求 ゲスト起動処理 プロセス起動

    VMのロード & JNIをフック ActivityThread改竄 (Application作成) ゲストプロセスの ActivityThread#mH#mCallbackに 処理を追加 Context# startActivity …
  52. #DroidKaigi_room5 # Activityの起動 - ゲストアプリプロセスでIntentを受信後、 H#dispatchMessage(msg)が呼ばれる - この中で通常は受信したIntentからActivity生成処理が走る - H#mCallbackにセットした処理により、Intentの宛先

    がゲストアプリの起動したいActivityに置き換わる - このIntentを基にActivityを生成し起動する
  53. #DroidKaigi_room5 # プロセスの状態 ホストアプリプロセス Managerプロセス ゲストアプリプロセス (ContentProvider) Activity起動要求 ゲスト起動処理 プロセス起動

    Context# startActivity Intent差し替え Intentから Activityを生成 Intent Target: io.virtualapp/.StubActivity Extra: Intent(x.guest/.MainActivity)
  54. #DroidKaigi_room5 # プロセスの状態 ホストアプリプロセス Managerプロセス ゲストアプリプロセス (ContentProvider) Activity起動要求 ゲスト起動処理 プロセス起動

    Context# startActivity Intent差し替え Intentから Activityを生成 Intent Target: x.guest/.MainActivity
  55. #DroidKaigi_room5 # なぜStubActivityを経由? - 別アプリから起動する場合を考慮した結果

  56. #DroidKaigi_room5 # 別アプリから起動する場合 別アプリ ゲストアプリプロセス (ContentProvider) startActivity ContentProvider 起動 mCallbackで

    Intent差し替え Intentから Activityを生成 StubActivity起動 Intent Target: io.virtualapp/.StubActivity Extra: Intent(x.guest/.MainActivity)
  57. #DroidKaigi_room5 # AndroidManifest.xmlの答え合わせ StubActivity (100個) StubContentProvider (50個) Activityの起動を仲介する ダミーのActivityたち ゲストアプリの

    プロセスになる奴ら
  58. #DroidKaigi_room5 # 補足:メソッドのフック # なぜ必要? - Intentの処理やCameraのインストールなどがパッケー ジ名に依存している - PackageCacheのパスを変えないとPermission

    Deniedになる - Binder#getCallingUid()の返すUidを偽装 - その他パッケージ改竄検出をすり抜けるため
  59. #DroidKaigi_room5 # Javaのリフレクション (Proxy) で改竄する処理 - startActivityなどIntent処理 - 改ざんしないと本物が起動してしまうため -

    バックアップなど多くのメソッドの挙動を削除 - 動作しては困るものは削除してしまう - オーディオ周りなどの挙動をホストの処理に置き換え - ネイティブ周りなどの関係でそのままでは動かないので などなど
  60. #DroidKaigi_room5 # JNIのメソッドをフックして行うこと - UIDを偽装 - ゲストのdataディレクトリを偽装 - Cameraの初期化 -

    AudioRecordのパーミッションを取得 - PackageCacheに使うパスを変更 詳しくはスライド最後の付録で
  61. #DroidKaigi_room5 マルチアカウントアプリの危険性

  62. #DroidKaigi_room5 # 広告をオーバーライドされるなどの問題 - 広告フレームワークの処理を変え、マルチアカウントア プリ作者に収益が入るようにすることも技術的には可能 - Parallel Space上のAdMobでは変更されていないことを確認 したが、他のアプリはどうかわからない

    - その他、Playストアでの課金をしたかのように振る舞わ せるなども可能と考えられる
  63. #DroidKaigi_room5 # 過剰にパーミッションを与えてしまう危険性 - マルチアカウントアプリに非常に多くのパーミッション が使われているため、ゲストアプリにも同様のパーミッ ションを与えてしまう危険性がある - ゲストアプリに悪意がある場合、AndroidManifestで はパーミッションを指定せず、マルチアカウントアプリ

    上で動く時のみパーミッションを用いる、などが可能 - そもそもマルチアカウントアプリ自体が大変危険なアプ リかもしれない
  64. #DroidKaigi_room5 # アカウントデータ・パスワード漏洩の危険性 - マルチアカウントアプリに悪意がある場合、容易にパス ワード入力情報から内部のデータまで全て盗むことが可 能 - ゲストアプリが悪意のあるものの場合も、他のゲストア プリの保存データを取得可能

    ↓ VirtualApp上の端末エミュレータアプリでLINEの情報を確認できた
  65. #DroidKaigi_room5 # 応用すると…… - 知らぬ間にアプリが置き換わっていることがあるかも…… - いろいろ頑張ればユーザーが気付かないようにアプリをアンイン ストールさせることが可能 - 代わりに悪意のあるアプリケーション上で動かすショートカット

    を作っておけば、ユーザーは置き換わっていることに気付くのは 困難 ⇒ 対策した方がよいかも……
  66. #DroidKaigi_room5 # マルチアカウントアプリの対策 - 単純な証明書のチェックはNG - マルチアカウントアプリ上では証明書を取得するメソッドも書き 換わっており、正しい証明書を得てしまう - パッケージ名やUIDのチェック等もNG

    - これも完全にホストのものに差し替えられると確認が困難
  67. #DroidKaigi_room5 # 2018年現在最適なチェック方法!! - psコマンドで確認(一部省略) $ ps USER PID NAME

    u0_a51 8561 ps u0_a51 29616 com.lbe.parallel.intl:mdserver u0_a51 29699 parallel.monitor u0_a51 30703 jackpal.androidterm u0_a51 30889 /system/bin/sh 自分のアプリでは発生し得ないプロセスが同じユーザーとして実行され ていることをチェックできる
  68. #DroidKaigi_room5 # 最後に(対策する前に) - マルチアカウントアプリが使われるアプリ ≒ ユーザーはアカウント切り替え機能を求めている ⇒ マルチアカウント機能の実装を検討すべきでは? -

    現在マルチアカウントアプリの紹介記事の中で危険性を 伝える記事はないので、正しく周知していくことが必要 - 必ずしも危険なものではないが、危険性は知るべき
  69. #DroidKaigi_room5 付録:JNIのフック処理の詳細 参考(中国語):https://www.jianshu.com/p/052b6dd45659

  70. #DroidKaigi_room5 # JNIの基礎知識 - JNIからJavaのメソッドを呼ぶ機能がある // C++ jclass clazz =

    env->FindClass("SomeJavaClass"); jmethodID methodId = env->GetStaticMethodID(clazz, "javaMethod", "()V"); env->CallStaticVoidMethod(clazz, methodId);
  71. #DroidKaigi_room5 # JNIの基礎知識 - JNIからJavaのメソッドを呼ぶ機能がある // C++ jclass clazz =

    env->FindClass("SomeJavaClass"); jmethodID methodId = env->GetStaticMethodID(clazz, "javaMethod", "()V"); env->CallStaticVoidMethod(clazz, methodId);
  72. #DroidKaigi_room5 # JNIのフック jmethodIDはMethod構造体のポインタ // C++ struct Method { ClassObject*

    clazz; // クラスを表す u4 accessFlags; // publicか、nativeか、などのフラグ … DalvikBridgeFunc nativeFunc; // ネイティブ関数のポインタ … };
  73. #DroidKaigi_room5 # JNIのフック jmethodIDはMethod構造体のポインタ // C++ struct Method { ClassObject*

    clazz; // クラスを表す u4 accessFlags; // publicか、nativeか、などのフラグ … DalvikBridgeFunc nativeFunc; // ネイティブ関数のポインタ … }; これを書き換えればOK
  74. #DroidKaigi_room5 # JNIのフック jmethodIDはMethod構造体のポインタ // C++ struct Method { ClassObject*

    clazz; // クラスを表す u4 accessFlags; // publicか、nativeか、などのフラグ … DalvikBridgeFunc nativeFunc; // ネイティブ関数のポインタ … }; 環境によって構造が異なる
  75. #DroidKaigi_room5 # JNIのフック jmethodIDはMethod構造体のポインタ // C++ struct Method { ClassObject*

    clazz; // クラスを表す u4 accessFlags; // publicか、nativeか、などのフラグ … DalvikBridgeFunc nativeFunc; // ネイティブ関数のポインタ … }; 環境によって構造が異なる ここのオフセットを知 る必要あり
  76. #DroidKaigi_room5 # JNIのフック ダミーのメソッドをJava, C++両側に定義 // Java static native void

    nativeMark(); // C++ void mark() {}
  77. #DroidKaigi_room5 # JNIのフック ダミーメソッドのポインタに一致するオフセットを探索 // C++ size_t start = (size_t)

    env->GetStaticMethodID( clazz, "nativeMark", "()V"); size_t target = (size_t) mark; int offset = 0; while (true) { if (*((size_t *)(start + offset)) == target) break; offset += 4; }
  78. #DroidKaigi_room5 # JNIのフック このオフセットを使って他のメソッドを置き換える // C++ size_t method = (size_t)

    someMethodId; void **nativeFuncPtr = (void **) (method + offset); *nativeFuncPtr = (void*) hookFunc;