Android用VPNクライアントの開発(パート1)

みなさんこんにちは! この記事を書いた理由は、Android向けのVPNクライアントアプリケーションに関する記事やレビューが多数ある中で、 VpnService APIを使用した開発の問題を説明する通常の記事は1つもないという認識にあります。 さらに、ほとんどの場合、アプリケーション開発者として、これらの問題に対して何もできません。



最初から始めましょう。 しばらく前に、当社はモバイルデバイスセキュリティの分野の研究に基づいて、Android向けの小さなアプリケーション( WebGuard )をリリースして、既に退屈しているすべての広告をブロックし、ブラウザーから作業するときのスヌーピングやウイルスからの保護を決定しました。 この機能を実装するには、多くの問題を解決する必要がありましたが、最も時間がかかったのはアプリケーショントラフィックの傍受と処理でした。 ブラウザー接続をインターセプトおよびフィルター処理するために、VpnService APIを使用することにしました。これは、バージョン4.0.3でAndroidに登場し、必要なすべての機能を提供します(ただし、この機能はさまざまな理由で機能しない場合がありますが、少し後で判明しました)。



使用されているテクノロジーについて少し



ここでは、このAPIが選ばれた理由と、「ルート化されていない」Androidデバイス上の別のアプリケーションアプリケーションのネットワークトラフィックをインターセプトするために一般的に存在するものを詳細に説明する価値があります。 実際には(さまざまな脆弱性を除いて)3つの方法しかなく、さまざまなゲーム内購入メカニズムを持つアプリケーション開発者がそれらについて知るのに役立ちます。 それらの多くは、アプリケーションのネットワークトラフィックの置換は「ルート化された」デバイス上でのみ可能であると考えているため、送信データに変更を加える「不正」アプリケーションが出現する可能性があります(かなり高い)が、送信データの保護を「強化」しません。



そのため、 最初の方法はローカルプロキシサーバーをインストールすることです。 Android向けのほとんどのアンチウイルスはこの方法を使用します。 トラフィックを傍受するには、 RFC 2068標準で説明されているHTTPプロキシサポートを実装し、WiFiネットワーク設定のモバイルネットワークの設定でプロキシサーバーまたはAPNを設定するだけで十分です。 この場合、データ転送は下図のように行われます。











しかし、この方法には多くの深刻な問題があります。





最後の問題を除くこれらすべての問題は、 2番目の方法で正常に解決されます。VpnServiceクラスとVpnService.Builderクラスを使用してVPNクライアントアプリケーションを作成することです。 アプリケーションがいくつかの関数を呼び出すだけで十分です(コードでは必須のチェックは省略されています)。



//  Activity      Intent intent = VpnService.prepare(PromptActivity.this); startActivityForResult(intent, VPN_REQUEST_CODE); //  
      
      





  //   onActivityResult     // requestCode == VPN_REQUEST_CODE && resultCode == RESULT_OK VpnService.Builder vpnBuilder = new VpnService.Builder(); vpnBuilder.addAddress(options.address, options.maskBits); vpnBuilder.addRoute("0.0.0.0", 0); ParcelFileDescriptor pfd = vpnBuilder.establish(); FileInputStream in = new FileInputStream(parcelFileDescriptor.getFileDescriptor()); FileOutputStream out = new FileOutputStream(parcelFileDescriptor.getFileDescriptor());
      
      





、およびすべてのアプリケーション(ルートとして実行されているものも含む)のすべてのTCP / IPトラフィックは、Androidが作成するTUNインターフェースにリダイレクトされ、アプリケーションはデバイスファイル/ dev / tun(または/ dev / tun0、/ dev / tun1など)、そこから発信ネットワークパケットを読み取り、それらを処理のためにリモートVPNサーバーに送信し(通常は暗号化された接続を介して)、次に着信ネットワークパケットを記録します。 VPNクライアントの接続がTUNで「ラップアップ」するのを防ぐため、アプリケーションによって作成されたTCPまたはUDPソケットでVpnService.protectメソッドが使用されます。



この場合、上記のスキームは次のようになります。











このメソッドには2つの機能があります。



  1. アプリケーションは、startActivityForResultの呼び出しを通じてVpnServiceを使用する権利を取得する必要があり、システムはこのダイアログをユーザーに表示します。











    さらに、Androidは、デバイスが再起動されるまでのみアプリケーションに権限を発行したことを記憶しているため、再起動後に再度権限を要求する必要があります。 判明したように(これも予想していなかった)、10分ごとに電話を再起動したいユーザー(「はい、数百人」©)がいます。このウィンドウはユーザーを悩ませます。



  2. 「悲しみ」は以前のものより少し大きいです。 VPNをオンにした後、Androidは、通知アイコンをキーの形で表示し、実際に修正された通知を表示します。クリックすると、いくつかの統計を確認できます。









    「カーテン」での通知。 通知アイコンとステータスダイアログ


    ここでは、多くの現在のアプリケーションが通知を「一度に」ハングアップするのが大好きで、ステータスバーが新年の飾りに変わるため、ユーザーの不満が予想されました。









    また、ユーザーが「切断」ボタンをクリックすると、AndroidはVPNを切断し、アプリケーションから権利を奪います。その後、VPNをアクティブにするには、startActivityForResultの呼び出しを通じて権利を再度要求する必要があります。


また、このメソッドには小さな追加の問題があります(以下の「機能」はカウントしません)-動作させるには、リモートサーバーが必要です。



サーバーの問題は、 3番目の方法 (わずかに変更された方法2)で解決されます-VPNクライアントアプリケーションには、アプリケーションとプロセス接続からのトラフィックを解析するTCP / IPスタックも含まれています(準備が整っているか、 欠陥があるため、自分で作成することもできます)プロキシサーバーとして。 次に、アプリケーションのトラフィック処理スキームが多少変更され、次のようになります。











これは、WebGuardで使用する方法です。 欠点のうち、以前の方法と比較して、1つだけ注意することができます-アプリケーションは通常ルート権限を必要とする「生の」ソケットを作成する必要があるため、TCPまたはUDP以外のプロトコル(またはそれらのプロトコル)の通常の処理の不可能性a。 話の内容を明確にするために、簡単な例を見てみましょう。ユーザーがADBを介してシェルを起動し、 ICMPエコー要求を送信する「ping www.ya.ruコマンドを実行します。 さらに、VPNクライアントアプリケーションは、/ dev / tunからIPパケットを読み取り、解析して、パケットに特定のサーバーへのICMPエコー要求が含まれていることを検出します。 また、アプリケーションはリクエストをさらにネットワークに送信できないため、2つのオプションしかありません。パケットを無視するか、目的のサーバーへの接続を試行するpingをエミュレートし、成功した場合は、/ dev / tunに偽のICMPエコー応答を記録します。



機能VpnService API



ユーザーがアプリケーションを開発し、最初のバージョンをテストして使用するプロセスで、VpnService APIに関連する多数のエラーまたは欠点に遭遇しました。 それらのいくつかは修正されました。本質的にこれらはプログラマーの欠陥であり(正直に書いてbash.org.ruにアクセスしました)、それ以外の場合はやや困難または不可能です。





このリストを読んで考えてみてください。おそらく彼のVPNでしょうか。 残念ながら、「ルーティングされていない」Androidフォンですべてのトラフィックを傍受する方法は他にありません。



NinePatchについて少し



上記のリストには別の問題は含まれておらず、その結果は私たちにとって非常に予想外でした(私たちだけでなく)。 したがって、結果についてより詳細に話すことにしました。 それはすべて、ある時点でアプリケーションの最も重要な部分である退屈なアイコンを作成する必要があったという事実から始まりました。 すぐに言ってやった。 美しい丸いアイコンが描かれ、突然リリースされたとき、プレリリースバージョンをテストしています。











一部の電話では、VPN通知のアプリケーションアイコンが非常に奇妙に表示されることがあります(色、サイズ、またはその他が正しくない)。 実験と短いが白熱した議論の後











、アプリケーションアイコンを9パッチ形式にするように命じられました。このテーマに関する公式ドキュメントの利点は何も言わず(つまり、禁止しません)、卵型の問題は解決されます。 しかし、リリースのリリース後、「犠牲者」は非常に早く現れました。 これらは、Androidアプリケーションとapkファイルで動作するさまざまなオンラインサービスの両方であり、アプリケーションアイコンを9パッチ形式で受け取ることを期待していませんでした。 3つの場所の台座に最も注目すべきものを配置することにしました。



3.アイコンを表示しようとしたときにクラッシュしたさまざまなランチャー(LauncherProなど)。



2. SamsungおよびYandexのAndroidアプリケーションを保存します。 アプリケーションをYandex.Storeにダウンロードしようとすると、「APKからアプリケーションアイコンを抽出できませんでした」という完全に理解可能なエラーの説明が発行されました。 C Samsung Appsはもっと楽しかったです。 同社はハイテク企業であるため、アプリケーションを追加する際のテスト結果は適切な形式になります。アプリケーションテストプロセスが記録され、エラーが表示されるビデオへのリンクが記載された電子メールです(理論上)。 しかし、すべてがいつものようになったことが判明し、ビデオではないリンクが付いた手紙が届きました。



1.まあ、名誉ある最初の場所は、右、ソニーがXperia電話で占有している。 リリースのリリース後しばらくして、Xperia Lの所有者は、WebGuardが電話を「殺した」というメッセージを送信し始めました( )。 インストール中にアプリケーションアイコンを処理しようとすると、PackageManagerServiceがクラッシュし、その後電話が自動的に再起動して無限にダウンロードすることが判明しました。



 E / AndroidRuntime(790):***システムプロセスの致命的な例外:Thread-123 <br />
 E / AndroidRuntime(790):java.lang.ClassCastException:android.graphics.drawable.NinePatchDrawableはandroid.graphics.drawable.BitmapDrawableにキャストできません<br />
 E / AndroidRuntime(790):com.android.server.pm.PackageManagerService $ SetIconCacheThread.run(PackageManagerService.javahaps672)<br />
 ... <br />
 E / AndroidRuntime(2981):致命的な例外:ApplicationsProviderUpdater <br />
 E / AndroidRuntime(2981):java.lang.RuntimeException:パッケージマネージャーが停止しました<br />
 E / AndroidRuntime(2981):android.app.ApplicationPackageManager.queryIntentActivitiesAsUser(ApplicationPackageManager.java:487)<br />
 E / AndroidRuntime(2981):android.app.ApplicationPackageManager.queryIntentActivities(ApplicationPackageManager.java:473)<br />
 E / AndroidRuntime(2981):com.android.providers.applications.ApplicationsProvider.updateApplicationsList(ApplicationsProvider.java ∗ 18)で<br />
 E / AndroidRuntime(2981):com.android.providers.applications.ApplicationsProvider.accessで$ 300(ApplicationsProvider.java:69)<br />
 E / AndroidRuntime(2981):com.android.providers.applications.ApplicationsProviderで$ UpdateHandler.handleMessage(ApplicationsProvider.java:206)<br />
 E / AndroidRuntime(2981):android.os.Handler.dispatchMessage(Handler.java:99)<br />
 E / AndroidRuntime(2981):android.os.Looper.loop(Looper.java:137)<br />
 E / AndroidRuntime(2981):android.os.HandlerThread.run(HandlerThread.java:60)<br />
 E / AndroidRuntime(2981):原因:android.os.DeadObjectException <br />
 E / AndroidRuntime(2981):android.os.BinderProxy.transact(ネイティブメソッド)<br />
 E / AndroidRuntime(2981):android.content.pm.IPackageManager $スタブ$ Proxy.queryIntentActivities(IPackageManager.java:2027)<br />
 E / AndroidRuntime(2981):android.app.ApplicationPackageManager.queryIntentActivitiesAsUser(ApplicationPackageManager.java:481)<br />
 E / AndroidRuntime(2981):...さらに7つ<br />




その結果、バージョン1.3からは、特にアイコンの表示に関するすべての問題を解決しても解決しなかったため、9パッチなしでアイコンを使用することが決定されました。









Ainol Novo10 HeroでのVPN有効化通知




おわりに



投稿を大きくしすぎたくないので、記事をいくつかの部分に分割することにしました。 次のパートでは、トラフィックをフィルタリングするAndroidアプリケーションが(androidによると)バッテリー電力を大量に消費し、DalvikVM!= JavaVMという1つのパフォーマンステストを例に挙げて思い出す理由を説明します。



この記事が、Android向けの興味深いアプリケーションの作成に役立つことを願っています。 良い開発を!



All Articles