モバイルアプリ開発者のための mitmproxy 入門

# はじめに
モバイルアプリを開発しているときに、アプリとサーバー間の通信を確認したいときがあります。たとえば、期待通りの HTTP リクエストが送られているか調べたり、サーバーからのレスポンスが間違っていないか確認したりする必要が生じます。

そんなときに、いちいちデバッガで止めても良いのですが、プロキシをはさめば簡単に通信を覗くことができます。しかも、レスポンスを改竄して、わざと不正なレスポンスにしてアプリがクラッシュしないかテストしたり、特定のリクエストだけブロックしてサーバー障害を擬似的に再現することができます。

# mitmproxy とは
mitmproxy は man-in-the-middle 型のプロキシサーバーのツールです。OS X や Windows、Linux 上で動作し、対話式の CUI を持ちます。SSL サポートをしている点が特長になります。

公式サイト: http://mitmproxy.org/doc/mitmproxy.html

# インストール
以下では、mitmproxy の導入方法について説明します。

## マシン側での準備
まず mitmproxy を手元のマシンに導入します。[pip](http://www.pip-installer.org/) を使ってインストールするのが一番簡単でしょう。pip については[ここ](http://pip.readthedocs.org/en/latest/installing.html)を参照してください。

```
pip install mitmproxy
```
でインストールできます。

なお、お使いのマシンが OSX の場合は[インストールガイド](http://mitmproxy.org/doc/install.html)にもあるように、XCode が必要なようです。

## モバイル端末の準備
次に、モバイル端末を設定して、手元のマシンで動いている mitmproxy をプロキシサーバーとして使うようにします。

### iOS
iOS の場合は、「設定」→「Wi-fi」に行って、いま接続しているネットワークをタップ → 「HTTPプロキシ」でプロキシの設定ができます。設定項目には以下を入力します。なお、ポート番号はデフォルトで 8080 ですが、後述のようにプロキシサーバー起動時に別の番号を指定可能です。

- サーバー：自分のマシンの IP アドレス
- ポート番号：8080
- 認証：オフ

![IMG_3019_filtered_s.png](https://qiita-image-store.s3.amazonaws.com/224/36306/23b998e2-9a81-5511-2ab5-8aa43e6daf47.png)  ![IMG_0635_filtered_s.png](https://qiita-image-store.s3.amazonaws.com/224/36306/4423ea25-a70f-fa2f-5f32-713c696c6f2b.png)


### Android
Android の場合は、「設定」→「Wi-Fi」に行って、いま接続しているネットワークを長押し →「ネットワークを変更」をタップして、開いたダイアログの「詳細オプションを表示」のチェックボックスにチェックを入れるとプロキシ設定の入力欄が表示されます。設定項目については、iOS と同じなので省略します。
![Screenshot_2014-08-31-12-24-23_filtered_s.png](https://qiita-image-store.s3.amazonaws.com/224/36306/9440dca1-77a2-2020-0b96-39cdb1402253.png)  ![Screenshot_2014-08-31-12-24-51_filtered_s.png](https://qiita-image-store.s3.amazonaws.com/224/36306/5e55593d-d769-2666-b081-11de9ef26dfe.png)


## 証明書のインストール
さて、まずは mdotmproxy を起動してみましょう。デフォルトではポート番号8080で起動しますが、`mitmproxy -p 8081` のように `-p` オプションを指定することで別の番号を指定可能です。

```
% mitmproxy
```

この時点で、モバイル端末でネットワークに繋ごうとすると、上記のプロキシを通るはずです。しかし、大抵はアプリ側で通信エラーが起きます。これは、端末に証明書が設定されていないために起こるものです。そこで、モバイル端末上のブラウザを開き `http://mitm.it` というアドレスにアクセスします。そうすると以下のような画面が表示されます。
![51e899bf-320d-e06b-8fe1-ebde803ad16a_m.png](https://qiita-image-store.s3.amazonaws.com/224/36306/b7e78257-17cc-2d50-dddc-637aa7f67b6f.png)

ここで、Android なら Android、iOS なら iOS のアイコンをタップすることで証明書をインストールすることができます。

# 基本的な使い方
それでは、さきほど起動した mitmproxy の画面を見てみましょう。モバイル端末でなにか操作をすると、行われた HTTP 通信の一覧が出力されるはずです。たとえば、以下は Play Newsstand を起動したときのものです。なお、`Shift + F`を入力すると`following`モードになって自動的に最新のリクエストが最下段に表示されます。

また、`E`キーを押すと、画面が2段になって下段にイベントログが表示されます。
![terminal.png](https://qiita-image-store.s3.amazonaws.com/224/36306/cc17dd6f-37dc-e7ff-2270-bde50afd9a51.png)


もし1つ1つの通信内容を見たいのであれば、もう一度`Shift + F`を入力して`following`モードを解除してから、`J`キーおよび`K`キーで上下にカーソルを動かして見たい通信を選びます。通信を選んで`Enter`キーで内容が見られます。

リクエストとレスポンスは`TAB`キーで切り替えられます。また、先程の通信リストに戻るには`Q`キーを押します。

![reqresp.png](https://qiita-image-store.s3.amazonaws.com/224/36306/af185482-933a-84b6-ba1b-9f249052d119.png)


## フィルタリング
このように、モバイル端末上のすべての通信を見ることができますが、通常は量が多すぎて目的の通信を探すのが大変です。mitmproxy 上で`L`キーを入力することで、フィルタリングすることが可能です。正規表現パターンに合致した URL で絞り込むこともできますし、ヘッダやメソッドで絞り込むこともできます。

以下の例では、`play.googleapis` というパターンで絞り込んだところです。
![filtering.png](https://qiita-image-store.s3.amazonaws.com/224/36306/443cd100-b1d5-db86-270a-c979e4baa3cb.png)

## インタセプト
また、特定の通信を止めて中身を書き変えるということもできます。mitmproxy 上で`I`キーを入力してからインタセプトしたい通信に合致するパターンを入力してください。使えるパターンはフィルタリングと同じです。

その状態で通信を行うと、下のようにインタセプトされた通信がオレンジで表示されます。
![intercept_01.png](https://qiita-image-store.s3.amazonaws.com/224/36306/916ac5e2-4539-85f7-289e-6607cf8850d0.png)

インタセプトした通信を書き変えるには、インタセプトした状態で`Enter`キーで通信を表示し、`E`キーを入力します。
![intercept_04.png](https://qiita-image-store.s3.amazonaws.com/224/36306/ed53377e-d692-081a-1341-7824c322fd29.png)

この状態で通信を書き変えてから、`Q`キーで先程の画面に戻り`A`キーを押すと通信が行われます。

書き変えはリクエスト時とレスポンス時の2回行えます。最初に`A`キーを押すとリクエストが許可され、レスポンスが返ってきたところでまたインタセプトされます。そこでさらにレスポンスを編集してから`A`キーを入力すると、改竄されたレスポンスが端末に届きます。
![intercept_02.png](https://qiita-image-store.s3.amazonaws.com/224/36306/9a339ac5-5984-1e24-11fa-eef5cab02acb.png)

![intercept_03.png](https://qiita-image-store.s3.amazonaws.com/224/36306/f3dd3e0f-d0e9-b479-b83c-cad76fdd5463.png)

もし複数の通信が同時にインタセプトされて、すべて通したい場合は`Shift + A`を入力します。

# 高度な使い方
mitmproxy には、さらに高度な使い方があります。ここではクライアントサイド・リプレイとスクリプトによる書き変えを紹介します。

## クライアントサイド・リプレイ
クライアントサイド・リプレイは、一度行った通信を再生する機能です。このとき、リクエストの内容を書き変えることができます。

mitmproxy の公式サイトでは、例として Apple Game Center のゲームのハイスコアを改竄する方法が載っています(一時、ランキングがすべて `2,147,483,647` すなわち `2^31 - 1` になっていたことがあるそうです)。

リプレイを行うには、再生したい通信の場所までカーソルを持ってきて`R`キーを押します。もし、事前に編集したい場合は、先に`Enter`キーで通信を表示し、`E`キーで編集します。編集が終わったら、`Q`キーで抜けて、先程と同じように`R`キーを押します。

リプレイが実行されると、通信の先頭に以下のようなリプレイ・マークが付きます。
![replay.png](https://qiita-image-store.s3.amazonaws.com/224/36306/87c5ef8b-e775-4dc5-43d1-520c085cc5d5.png)



## スクリプティング
通信を改竄する簡単な方法はインタセプト機能を使う方法ですが、いちいちリクエストやレスポンスを手動で書き変えるのは面倒ですし、編集している間に通信がタイムアウトしてしまうこともあります。そういうときは、スクリプティング機能を利用すると良いでしょう。

たとえば、とあるニュース・アプリが、記事リスト更新時に `/api/v1/list.json?type=reload` という REST API を利用しているとします。このアプリの通信を改竄して、サーバーからのレスポンス内容を機械的に置換したいとしましょう。そのようなときは、以下のスクリプトを`test_mitmproxy.py`という名前で保存して、`-s`オプション付きで mitmproxy を起動します。

```py
# -*- coding: utf-8 -*-
def response(context, flow):
    path = flow.request.path
    query = flow.request.get_query()
    if path.endswith("/api/v1/list.json") and query.in_any('type', 'reload'):
        flow.response.replace("艦これ", "hogehoge")
```

```bash
mitmproxy -s test_mitmproxy.py
```

この状態で、アプリを起動して該当の API 呼び出しを発生させる(たとえば、記事リストをリロードする)とレスポンス中の「艦これ」がすべて「hogehoge」に書き変わっているはずです。

また、スクリプト中では、引数を取ることができるようになっています。たとえば、以下のスクリプトは通信をランダムに切断することで擬似的に不安定な通信環境を再現するものです。

```py
# -*- coding: utf-8 -*-
import random

def start(ctx, argv):
    if len(argv) != 3:
        raise ValueError('Usage: -s "pseudo_unstable_network.py hostname percentage(0-10)"')
    # You may want to use Python's argparse for more sophisticated argument parsing.
    ctx.arg1, ctx.arg2 = argv[1], argv[2]

def request(context, flow):
    host = flow.request.host
    if host.endswith(context.arg1) and random.randint(0, 10) <= int(context.arg2):
        context.log("Kill the request for %s. Host: %s" % (flow.request.get_url(), host))
        flow.kill(context._master)
```

このスクリプトを指定するときは、以下のように引数を指定します。この例では `api.example.com` との通信を 80% の確率で切断します。

```bash
mitmproxy -s "pseudo_unstable_network.py api.example.com 8"
```

# おわりに
いかがでしたでしょうか。mitimproxy を使うと、このように簡単に通信内容を覗いたり書き変えを行うことができます。

みなさんも、mitmproxy を利用して、ぜひ快適なモバイルアプリ開発生活をお送りください。