mitmproxyでiOS Simulatorの特定URLへのHTTP/HTTPSの応答bodyを書換える

MacOS全体のプロキシ設定をローカルで起動したmitmproxyにセットして、iOS Simulator のHTTP/HTTPS通信を採取・変更するメモ。

## mitmproxy の基本設定

mitmproxyをインストール (version 0.17.1)：

```shell-session
$ brew install mitmproxy
```

初回起動時に `~/.mitmproxy/` 以下にCA証明書が生成される：

```shell-session
$ mitmproxy    # Ctrl-C で抜ける
```

Macのプロキシ設定をON/OFFする定型処理を足した下のスクリプトを　`mitmproxy-wrap` などの名前で保存して実行できるようにする：

```sh
#!/bin/sh
# save as mitmproxy-wrap

sudo networksetup -setwebproxy Wi-Fi localhost 8080
sudo networksetup -setsecurewebproxy Wi-Fi localhost 8080
sudo networksetup -setwebproxystate Wi-Fi on
sudo networksetup -setsecurewebproxystate Wi-Fi on
mitmproxy "$@"
sudo networksetup -setwebproxystate Wi-Fi off
sudo networksetup -setsecurewebproxystate Wi-Fi off
```

## iOS Simulator の証明書設定
XcodeでiOS Simulatorで1つ以上のデバイス(OS)を起動＆終了しておく。
[ADVTrustStore](https://github.com/ADVTOOLS/ADVTrustStore)にある `iosCertTrustManager.py` を使ってmitmproxyのCA証明書をiOS Simulatorの各デバイスにインストール：

```shell-session
$ ./iosCertTrustManager.py -a ~/.mitmproxy/mitmproxy-ca-cert.pem
subject= CN = mitmproxy, O = mitmproxy

Import certificate to iPhone 6 v10.0 [y/N] y
Importing to /Users/...(snip).../Library/Developer/CoreSimulator/Devices/...(snip).../data/Library/Keychains/TrustStore.sqlite3
  Existing certificate replaced
...(snip)...
```

## mitmproxyでHTTP応答を書換える

フィルタのスクリプトを準備：


新しいmitmproxyの場合 (2.0.2で動作確認):

```python
# Usage: -s "replace_response_body.py <url_prefix> <file>"
import argparse

class Replacer:
    def __init__(self, url_prefix, file_name):
        self.url_prefix, self.file_name = url_prefix, file_name

    def response(self, flow):
        flow.response.replace(self.url_prefix, self.file_name)
        if flow.request.pretty_url.startswith(self.url_prefix):
            with open(self.file_name, "r") as file:
                flow.response.text = file.read()

def start():
    parser = argparse.ArgumentParser()
    parser.add_argument("url_prefix", type=str)
    parser.add_argument("file_name", type=str)
    args = parser.parse_args()
    return Replacer(args.url_prefix, args.file_name)
```


古いmitmproxyの場合 (0.17で動作確認):

```python
# Usage: -s "replace_response_body.py <url_prefix> <file>"
from mitmproxy.models import decoded

def start(context, argv):
    if len(argv) != 3:
        raise ValueError('Usage: -s "replace_response_body.py <url_prefix> <file>"')
    context.url_prefix, context.file = argv[1], argv[2]

def response(context, flow):
    if flow.request.pretty_url.startswith(context.url_prefix):
        with decoded(flow.response):
            with open(context.file, "r") as file:
                flow.response.content = file.read()
```

書換結果のファイルを準備：

```javascript
// save as sample.js

alert('hello mitmproxy');
```

下は `https://www.google-analytics.com/analytics.js` で始まるURLのHTTPSの応答bodyを `sample.js` で書換えるサンプル：

```shell-session
$ mitmproxy-wrap -s "replace_response_body.py https://www.google-analytics.com/analytics.js sample.js"
```

iOS Simulatorを起動しSafariなどのブラウザで該当URLへのリクエストが発生すると、sample.jsが返却・実行されて下のダイアログが出る：

![capture.png](https://qiita-image-store.s3.amazonaws.com/0/7006/9ddb1278-a64d-a4df-f672-51d135ada17b.png)

## 参考にしたページ
- [mitmproxyでiOS Simulatorを覗くためのメモ | hmhv](http://hmhv.info/2015/10/mitmproxy-with-ios-simulator/)
- [iOS実機のSSL通信をプロキシによって傍受したり改ざんする方法 - Qiita](http://qiita.com/yimajo/items/c67cb711851f747c35e5)
- [mitmproxy/examples at v0.17.1 · mitmproxy/mitmproxy](https://github.com/mitmproxy/mitmproxy/tree/v0.17.1/examples)