Unityで作成したAndroidアプリのhttp/https通信をキャプチャする

#概要
Androidアプリの通信がどのように見えるのか気になったので、パケットキャプチャして通信内容を見る方法について調べてみた。思いのほか大変だったので情報をまとめておく。

筆者の環境はPCがWin10でスマホがAndroid8.1。

#1.Unityで適当に通信するアプリを作る

通信するクラスを適当に作成。
テストがしたいだけなので、なんか良い感じのサーバないかな？と思ったらhttpbinなる便利なサイトを発見。使わせて頂きます。圧倒的感謝。使い方その他は以下のサイト様が分かりやすいです。
https://www.softantenna.com/wp/review/httpbin/


``` c#:WebRequestTest.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class WebRequestTest : MonoBehaviour
{
    public string URL;
    [SerializeField] private Dropdown dropdown;

    public IEnumerator WwwRequestTest(string url)
    {
        WWW www = new WWW(url);
        yield return www;
        Debug.Log(www.text);
    }

    public IEnumerator UnityWebRequestTest(string url)
    {
        using (UnityWebRequest request = UnityWebRequest.Get(this.URL))
        {
            yield return request.SendWebRequest();
            if (request.isNetworkError || request.isHttpError)
            {
                Debug.Log(request.error);
            }
            else
            {
                Debug.Log(request.downloadHandler.text);
            }
        }
    }

    public void HttpWebRequestTest(string url)
    {
        try
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

            Debug.Log("Proxy.Credentials: " + request.Proxy.Credentials);

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            Stream stream = response.GetResponseStream();
            string webContext = new StreamReader(stream).ReadToEnd();
            Debug.Log(webContext);

        }
        catch (WebException webException)
        {
            var ex = webException as Exception;
            while (ex != null)
            {
                Debug.Log(ex.ToString());
                ex = ex.InnerException;
            }
        }
    }

    public void OnWWWButtonClicked()
    {
        Debug.Log("OnWWWButtonClicked");
        Debug.Log(this.URL);

        StartCoroutine("WwwRequestTest", this.URL);
    }

    public void OnUnityWebRequestButtonClicked()
    {
        Debug.Log("OnUnityWebRequestButtonClicked");
        Debug.Log(this.URL);

        StartCoroutine("UnityWebRequestTest", this.URL);
    }

    public void OnHttpWebRequestButtonClicked()
    {
        Debug.Log("OnHttpWebRequestButtonClicked");
        Debug.Log(this.URL);

        this.HttpWebRequestTest(this.URL);
    }

    public void OnURLChanged()
    {
        var keyMap = new Dictionary<int, string>();
        keyMap.Add(0, "http://httpbin.org/ip");
        keyMap.Add(1, "https://httpbin.org/ip");

        this.URL = keyMap[this.dropdown.value];
    }
}

```

以上のスクリプトを適当なGameObjectに追加して、適当にボタンとurlを変更するドロップダウンメニューを作成する。

![android_http_test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101808/c72cabfe-0e15-0d43-2e2e-c9bae406c956.png)

エディタ上で動作することを確認したあと、apkをビルドして実機にインストールする。

通信ができているかどうかの確認はDebug.logの出力で確認できる。Android実機で確認する場合のadbコマンドは以下。

```
adb logcat | findstr -i unity
```

#2 Fiddler

![android_http_test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101808/ee411b15-1268-e350-1646-25d9e7b834eb.png)


https通信のデータの中身まで見る方法を調べてみたところFiddlerというツールを使えば見れるらしい。Fiddlerの他にも[同類のツール](https://qiita.com/ishigaki_shun/items/41ab31b002c8649be2c2)はいろいろある様子。

導入方法その他は以下のサイト様の記事が分かりやすい。
https://amimilab.com/2016/08/14/post-183/

Fiddlerで立てたプロキシサーバ経由で通信することで、Fiddler上で通信の中身を全部みれるようになるらしい。
**しかし、上記のサイト様を参考にFiddlerを試してみても、https通信はキャプチャできなかった。**調べてみたところAndroidのca証明書に関する仕様が変わったのが原因らしい。

###Androidの通信まわりの仕様変更
Android7で特別に設定されていない限り、カスタムされたca証明書を無視する仕様になったらしく、Fiddlerが生成したca証明書をAndroid端末にインストールしてもこの証明書は無視されてしまうようだ。アプリ側で使用するca証明書を明示する必要性があるらしい。

![android_http_test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101808/cbad5f41-fec2-12ee-ba12-8bd56a38624b.png)

https://developer.android.com/about/versions/nougat/android-7.0#default_trusted_ca

余談だがAndroid9ではデフォルトでhttp通信ができないようになっているらしいので、Android9以上の端末を使用する場合は注意が必要です。
https://developer.android.com/about/versions/pie/android-9.0-changes-28?hl=ja#apache-p

###カスタムca証明書を利用して通信するように設定する

Android公式のドキュメントを[参照](https://developer.android.com/training/articles/security-config?hl=ja#ConfigCustom)するとAndroidManifest.xmlのapplicationの項目に `networkSecurityConfig`というプロパティ？を記述して、さらに設定ファイルを追加する必要があるらしい。

UnityでapkをビルドするときはAndroidManifest.xmlをUnity先生が勝手に良い感じに作成してくれているが、これを編集する必要があるようだ。これに関しては以下のサイト様が詳しい。
https://blog.wizaman.net/archives/1026

`Assets/Plugins/Android`ディレクトリ配下にAndroidManifest.xmlを追加するとビルドするときにUnity先生が良い感じにマージしてくれるらしい。雛形のAndroidManifest.xmlの入手方法は、[公式](https://docs.unity3d.com/ja/current/Manual/android-manifest.html)によるとapkをビルド後にAndroidManifest.xmlがキャッシュされているらしいのでこれをコピペして使うのが手軽かもしれない。場所は`Temp/StagingArea/AndroidManifest.xml `

自分はビルドしたapkファイルを`apk_tool`でデコンパイルして入手した。

`Assets/Plugins/Android`ディレクトリ配下にAndroidManifest.xmlをコピペしたらこれを編集する。application のところに`android:networkSecurityConfig="@xml/network_security_config"`と記述する。

```
        <application android:networkSecurityConfig="@xml/network_security_config" >
        </application>    
```

https://developer.android.com/training/articles/security-config?hl=ja#manifest

次に`Assets/Plugins/Android/res/xml`ディレクトリを作って配下に適当なテキストエディタかなんかで`network_security_config.xml`ファイルを作成する。内容は以下。

```
   <network-security-config>
        <base-config>
            <trust-anchors>
                <certificates src="system"/>
                <certificates src="user"/>
                <certificates src="@raw/fiddlerroot"/>
            </trust-anchors>
        </base-config>
    </network-security-config>
```

ちなみにこのnetwork_security_config.xmlファイルについてググってると`<certificates src="user"/>`だけでイケるっていう情報があったが自分はだめだった。上記のようにca証明書を直接指定してやったらイケた。

ということで、次にFiddlerが`http://ipv4.fiddler:8888/`で生成してくれるca証明書をdlして`Assets/Plugins/Android/res/raw/` 配下に追加する。このときca証明書は`FiddlerRoot`っていう名前になっていると思うが、AndroidManifestでは大文字は使えないので、リネームする必要があることに注意。自分は適当に`fiddlerroot`に変更した。

以上を設定できたらUnityでapkをビルドしてAndroid端末にインストールする。あとはFiddler上でhttp/https通信の中身を全部見れるようになる。