偽のVPNでAndroidのHTTPを検査する


あなたが検査することができます&デバイス上のすべての他のアプリからネットワークトラフィックを書き換えることができるAndroidのアプリを構築できますか?
で、はい、あなたがすることができます.HTTP Toolkit これは完全には、完全にデバイス内の偽のVPN接続をシミュレートするAndroid VPN APIの上にアプリを構築することによって行います.
ここでは、どのように動作し、それを実現するコードを見て話をしたいとどのように自分のために同じことを行うことができますを示します.
明確にするために、これはデバイスからのトラフィックのセキュリティに対する攻撃として意図されていません.あなたが実際にこのAndroidを行うとき、セットアップの間、明確な警告と許可をユーザーに提供して、これが活発であるどんな永続的なUI通知も必要とします.加えて、これはデフォルトで暗号化されたトラフィックの内容を読むどんな方法も与えません(次のポストでは、HTTPツールキットがどうするかについて話します).
開発者のツールのために、これが開くいくつかの興味深い&建設的なユースケースがあります.例えば、
  • テストとデバッグのためのモバイルトラフィックの検査と書き直しHTTP Toolkit 'sレーゾンd 'は、tre.
  • あなたのカスタムルールに従って発信アプリの接続をブロックするAndroidのためのファイアウォールを構築する.
  • 録音メトリックのトラフィックを送信&お使いのデバイスによって受信されます.
  • 遅延の追加による接続問題のシミュレーション
  • アンドロイドvpnはどのように動作しますか?

    The Android developer docs have a VPN guide , どちらが良い出発点ですか.
    これらのVPN APIを使用すると、アクティブ化されたときにネットワークトンネルインターフェイスをバックアップするファイルディスクリプタが与えられたアプリケーションでサービスを登録できます.
    そのトンネルインタフェースは、それからすべてのネットワークトラフィックのために全装置によって使われます.そのうえ、あなたのVPNサービスはこのトンネルを使用しない保護されたソケットを作成するために力を与えられます、したがって、VPNアプリはそれ自体を通り抜けることなくネットワークと通信することができます.
    一旦これが起動されると、アプリケーションがいくつかのデータを送るとき、ネットワークに行くのではなく、それぞれのIPパケットはこのファイル記述子の後でバッファーされます.あなたがそれから読むとき、あなたは直接生のネットワークバイトを与えられます、そして、あなたがそれにバイトを書くとき、彼らはネットワークインタフェースから直接受信されたバイトとして扱われます.
    これはあなたのアプリケーションのVPN接続を実装できるように設計されます.その場合、あなたのアプリケーションは、デバイス上のそれらの実質的な処理なしで、いくつかの保護された別々の接続の上に直接VPNプロバイダーにすべての読取りバイトを進めます.VPNプロバイダはそれからVPNのトラフィックの一部としてデータを転送し、あなたの接続の上であなたのアプリケーションに戻る応答パケットを戻します、そして、あなたは結果として生じるパケットをファイル記述子に書きます.
    これは主に設計されているものですが、それは私たちがそれを行うことができるすべてであることを意味しません.

    VPNはVPNではないか?

    Once we have a VPN service running, our app will receive every network byte the device sends, and has the power to inject raw bytes back.

    Things get interesting if rather than forwarding these bytes to a VPN provider, we examine them, and then simply put them straight back on the real network. In that case, we get to see every network byte, but we don't interfere with the network connection of the device, and we don't need an externally hosted VPN provider to do it.

    Unfortunately that's easier said than done. Our file descriptor works with raw IP data, but Android doesn't actually have an API for us to send raw IP data anywhere else. Instead, we have higher level APIs for TCP and UDP, and the IP part is always done invisibly under the hood.

    If we want to proxy these bytes, we need to match these two APIs up. We need to:

    • When we read an IP packet from our tunnel:
      • Parse the raw packet bytes into an IP packet.
      • Parse the TCP/UDP packet within and extract its content.
      • (For TCP) Track the connection state of the overall TCP connection, and ack/fin/etc each packet in the session appropriately.
      • Send the equivalent TCP/UDP content upstream, using Android's TCP/UDP APIs.
    • When we receive a TCP/UDP response from upstream:
      • (For TCP) Match that to the tunnelled TCP connection.
      • Build our own complete TCP/UDP + IP packet data around the received data.
      • Write the resulting bytes back into the tunnel.
      • Cleanly (or messily) close connections when the upstream socket is done.

    This is quite complicated. We effectively need to reimplement UDP & TCP from scratch!

    Fortunately, we're not the first people to want to do this. Most of the existing implementations are unmaintained demos, but they are open-source so we can build upon them! My own solution is based on a GitHub proof-of-concept called ToyShark (wiresharkのpunの上で、私は仮定します)は、順番に呼ばれていましたApplication Resource Optimizer ( source ).
    結果のHTTPツールキットAndroidアプリは上記のすべてを実装します.これは100 %無料とオープンソースですgithub.com/httptoolkit/httptoolkit-android ) 将来的に同様のオープンソースの実装が順番に構築できます.
    この実装はVPNとして動作します.一方、すべてのトラフィックを実際のネットワークにプロキシーしますJava NIO .
    コアVPNの実装はsrc/main/java/tech/httptoolkit/android/vpn , また、実装の詳細についてはREADMEがあります.我々はそれを拡張する方法を見て、我々はもう少し以下を探索します.
    もちろん、すべてのネットワーク帯域幅とレイテンシでパフォーマンスペナルティがあります.衝撃は、どんな現代の装置ででも通常の使用で本当に顕著でありません.携帯電話の接続では、通常は、基本的な接続パフォーマンスによって矮小化され、無線LANでもかなりの数に達することができます.

    これはおそらくJavaコードをネイティブコードとして書き直すことによってさらに改善されるかもしれませんが、それはかなりの複雑さを伴います.HTTPツールキットを使用する場合(ターゲットのデバッグではなく、重い日常的な使用)それは価値がない.
    その場で、我々は現在、透過的にデバイスからすべてのネットワークパケットを受信します.私たちは、私たちが望むように、それを検査することができますも、生のIPストリームまたは解析TCP/UDPパケットデータを介して、そのトラフィックを編集します.どのように終わるか

    どうやって使うの?

    In HTTP Toolkit's case, the usage of this is very direct: we forcibly redirect all HTTP(S) traffic via the debugging proxy (which is running on your local development machine). That proxy then lets you inspect and rewrite all the traffic there as you see fit.

    There's a demo video on HTTP Toolkit's Android page あなたが行動でこれを見たいならば.
    これを行うには、送信先のTCP接続のターゲットポートをチェックし、設定されたHTTPポート(例えば、80 , 443 , ...)のいずれかのアドレスを書き換える.次の行を追加するだけでTCP session setup :
    public Session createNewTCPSession(int ip, int port, int srcIp, int srcPort)
            throws IOException {
        // ...
    
        String ips = PacketUtil.intToIPAddress(ip);
    
        // Use the given target address, unless tcpPortRedirection has specified
        // a different target address for traffic on this port:
        SocketAddress socketAddress = tcpPortRedirection.get(port) != null
            ? tcpPortRedirection.get(port)
            : new InetSocketAddress(ips, port);
    
        channel.connect(socketAddress);
    
        // ...
    }
    
    強制的にすべての一致するトラフィックをプロキシに送信し、すぐにHTTPトラフィックに完全な可視性を与える.Androidの10でも設定a VPN proxy configuration , これは、明示的にマッチしないポート上のほとんどのトラフィックをキャッチします(ただし、その場合はデフォルトの設定ではなく、強制リダイレクト).
    それはリモート検査&書き換えのためのトラフィックをリダイレクトするのに十分です.他にどのようにこれを拡張できますか?最初に述べた3つのユースケースについて話しましょう.

    接続の停止

    To block outgoing connections to specific addresses or on specific ports, you just need to throw away the packets after you receive them from the VPN interface, once you've parsed them to work out where they're going.

    You can use this to block specific hosts you don't like, block DNS requests for certain addresses to build an on-device Pi-Hole , または、ホストの短い信頼できるリストだけに完全にあなたのネットワークを完全にロックするのを許します.
    HTTPツールキットの実装では、SessionHandler handlePacket はデバイスが送信したいrawパケットデータを扱う場所です.次のようになります.
    public void handlePacket(@NonNull ByteBuffer stream)
            throws PacketHeaderException, IOException {
        final byte[] rawPacket = new byte[stream.limit()];
        stream.get(rawPacket, 0, stream.limit());
        stream.rewind();
    
        final IPv4Header ipHeader = IPPacketFactory.createIPv4Header(stream);
    
        // TODO: inspect ipHeader here, and 'return' to drop the packet
    
        if (ipHeader.getProtocol() == 6) {
            handleTCPPacket(stream, ipHeader);
        } else if (ipHeader.getProtocol() == 17) {
            handleUDPPacket(stream, ipHeader);
        } else if (ipHeader.getProtocol() == 1) {
            handleICMPPacket(stream, ipHeader);
        } else {
            Log.w(TAG, "Unsupported IP protocol: " + ipHeader.getProtocol());
        }
    }
    
    それから、我々は単にIPヘッダーの目標アドレスまたはポートに基づいてパケットを落とすことができます.
    ここでパケットを落とすことは文字通りパケット損失です、そこで、アプリケーションが元の要請を送ることは全くどんな反応も決して聞きません.
    あるいは、より複雑な規則のために、あなたは特定のプロトコル処理の中で変化をすることができますhandleTCPPacket or handleUDPPacket 上記のメソッド.どちらの場合でも、解析されたTCP/UDPパケットを調べることができます(そして、TCPケースでは、接続が失敗したアプリを伝えるために即時のRSTパケットを注入します).

    記録トラヒック計量

    Want to know what your device sends and receives? Normally Android makes that more or less invisible. Within a fake VPN application like this though you have every network byte, so it's easy to examine and record data about outgoing & incoming packets.

    It's simplest to do total byte metrics by address and/or port, but you could also build more complex analyses of packet data itself. E.g. tracking the duration of TCP sessions with certain hosts, recording metrics about the unencrypted data available, or looking at DNS UDP packets to examine which hostnames you're looking up.

    For this codebase, we can easily capture outgoing traffic in the handlePacket method above. We have the raw IP packet data there, and the full TCP & UDP data is just a little more parsing away.

    To track incoming traffic, we'd need to look at the code that handles the upstream connections. For example in readTCP からSocketChannelReader , 上流のTCPデータを受信するとき:
    private void readTCP(@NonNull Session session) {
        // ...
    
        try {
            do {
                len = channel.read(buffer);
                if (len > 0) {
                    // We're received some TCP data from the external network:
                    sendToRequester(buffer, len, session);
                    buffer.clear();
                } else if (len == -1) {
                    // The external network connection is finished:
                    sendFin(session);
                    session.setAbortingConnection(true);
                }
            } while (len > 0);
        }
    
        // ...
    }
    
    この時点で、TCP接続の内容を処理しています.
    ここで読んだTCPデータを調べて、それをTCPセッションのIP &ポートに関連づけることによって、あなたはすぐにあなたの装置のネットワーク通信に視点を構築することができます.

    接続問題のシミュレーション

    It's possible to simulate connection issues on the device too. That's especially useful to test how applications handle low-quality internet connections and network errors.

    Unfortunately you can't simulate all issues, as Android's APIs give us limited control of upstream traffic. We control the contents of upstream TCP & UDP packets, but not the raw network connection itself. That means, for example, we can't simulate our device sending the wrong upstream packet sequence number or corrupting a TCP checksum, but we can simulate the device receiving such packets.

    There's still a lot of interesting things you can simulate with this:

  • Incoming or outgoing packet loss, where some packets simply disappear in transit.
  • Repeated or misordered packets.
  • Random connection resets (similar to tcpkill ).
  • いずれかの方向にパケットへの遅延.
  • 各ケースでは、通常、確率的に10 %の接続が失敗するか、またはパケットが平均で500 ms遅れるようにします.
    これを行うと、しばしばあなたのアプリケーションのいくつかの驚くべき結果とエラーが表示されます.効果的に我々はデバイス上でやっているchaos engineering .
    このようなランダム接続リセットを追加すると、通常は非常に目に見えるTCP接続の失敗になります.
    一方、パケットの損失や順序付けの問題は、通常、TCPコードレベルで処理されますが、アプリケーションコードには目に見えませんが、そうする過程は予測できない性能をもたらし、アプリケーションレベルで本当の問題を引き起こす可能性があります.
    毎日の開発中に、それは非常にこれらの問題を参照してください、高速で信頼性の高い無線LANをあなたのオフィスや自宅で与え、このような農村2 Gの問題をシミュレートする目の開口部をすることができます!
    あなたは非常に低いレベルでこれのほとんどをします、ちょうど個々の生のIPパケットがVPNから、そして、VPNから渡される所にフックするだけです.TCPエラーシミュレーションのために、あなたはTCP接続自体についてのより多くの情報を必要とします.
    HTTPツールキットの具体的には

  • ClientPacketWriter 生のIPデータがVPN(入って来るIPパケット)に書かれるところです.この段階で、我々は簡単にIPレベルで着信パケットを落とすか、破損するか、遅らせることができます.

  • handlePacket 再びSessionHandler , 私たちは、遅延、またはそうでなければ発信パケットに反応することができます.
  • SessionHandlerは、各パケットを処理する各接続のTCPフローを制御します.例えば、あなたは replySynAck 接続のリセットをスケジュールするにはresetConnection ) 彼らがつくられたあと、新しい接続2 sの50 %のために.

  • SessionManager 制御状態SessionHandler . そこにアクティブな接続のリストを考えると、我々はランダムなアクティブなTCPセッションを選択することができますし、好きな基準に応じてそれらを殺す.
  • 我々が見たように、Android VPN APIは強力です、そして、ここで多くの可能性があります.
    ネットワークトラフィックにフックするようないくつかのトリックでは、あなたが構築することができます興味深いツールの全世界があります.それを行ってください!任意の考えやフィードバックがありますか?私に知らせてください、あるいは、下記のコメントで.
    次のレベルにあなたのAndroidのデバッグをしたいですか?HTTP Toolkit あなたがワンクリックのHTTP(s)の検査&任意のAndroidアプリ(プラス他の多くのツールのためのモッキング)を与える.