
HTTP上の画像をすり替える悪いルーターの制作
9月の話ですが、Raspberry Piで、HTTP上を流れる画像をすり替える悪いルーターを作りました。
軽い中間者攻撃実験とも言えると思います。
小俣光行『ルーター自作でわかるパケットの流れ』(ルーター自作本)をベースに、それを改造して、ルーティングテーブル、NAPTを実装してから、画像のすり替えを実装しました。
丸10日かかりました…
ソースコード: https://github.com/iciclize/payload/tree/yjsnpi
デモ映像
HTTP上の画像をすり替えるルーターできました
— いの (@iciclize) September 10, 2019
絵面が想像よりだいぶヤバくてこれはいけない。
Cで書いてRaspberry Piで動かしていて、どうにも無線AP化できなかったのでとりあえず有線で動かしてます
本当は #KLabExpertCamp のときにできてたらよかったのに pic.twitter.com/yRCkwHhgfv
やってること
2つインターフェースのあるRaspberry Piの、WAN側としておうちのルーター、LAN側としてWindowsのホストをつないで、Windowsのブラウザと外部のHTTPサーバーとの通信を書き換える実験をしています。
- パケットを監視して、LANのホスト(以下クライアントやブラウザと言ったりします)と, 外部ホスト80/TCP(HTTPサーバー)とのTCPコネクションの開始を検出して追跡.
- それぞれのシーケンス番号, ACK番号を記録しておく.
- サーバーからの最初のレスポンスに
Content-Type: imageが含まれていたら, そのパケットを破棄し, サーバーにRSTパケットを送ってサーバーとの接続を強制切断.- 今回はサーバーから送られてくる画像データは一切必要ないので. もう送ってこないでということで.
- 用意しておいたニセのHTTP画像パケットをクライアントに送りつける.
- レスポンスヘッダには
Connection: closeをつけておいて, 画像の受信が完了したクライアントが自分からTCPコネクションを切断するように仕向ける.
- レスポンスヘッダには
なおルーター実行時、サーバーからの応答TCPパケットを、ルーターより先にカーネルが処理してしまって、サーバーに対しカーネルが勝手にRSTパケットを返してしまい、うまくクライアント・サーバー間でTCPがつながらなかったので、
iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP
としてLinuxからRSTが一切送れないようにして実験しました。
なので、後から気づきましたが、実験時送ったRSTは消されていて、実際にはRSTを送れていません。
経緯・動機
もともと昨年、画像をすり替えることを目標に自作ルーターをしていたのですが、画像をすり替えられるまでに至らず、画像のすり替えは保留としていました。
TCP/IPプロトコルスタック自作インターンに参加することになり、ちょうどいいタイミングだと思って、インターン中に画像のすり替えにチャレンジしました。(全然インターン中に終わらず延長線になりましたが。)
ルーティングテーブルの実装
実はルーター自作本のソースコードにはルーティングテーブルに対応する記述はないので自分で簡 単に作る必要があります。
- 宛先アドレス
- サブネットマスク
- ゲートウェイ
- 出口インターフェース
の4フィールドを持ったルーティングテーブルをテキストファイルで保存しておき、ごくシンプルに、対応するエントリを線形探索して経路を決定するようにしました。
NAPTの実装
ルーティングテーブルだけあってNAPT(IPマスカレード)の機能がなければどうなるかというと、LANからやってくるパケットの送信元アドレスがWAN側のアドレスに変換されずそのままWANに流れてしまうため、そのパケットが戻ってくる先のアドレスがプライベートアドレスとなってしまい、通信が成立しません。
つまり、上位にNAPTをしてくれるルーターが別に必要になるということで、これはちょっとダサい。
なのでまずNAPTを自前で実装しました。といってもRFCなど読まずに作ったので非常に簡易的ですが。
上位プロトコルはTCP, UDPのみに対応しています。ICMPのNAPT(?)には対応していないのでWAN-LAN間でのpingは通りません。
LAN側からWAN側への外向きパケットに対するNAPT
まず、インターフェース番号0をWAN側とし、それ以外をLAN側と決めておくことにします。
LAN側からWAN側に向かうパケットがやってきたら
- 送信元アドレス
- 送信元ポート番号
- 宛先アドレス
- 宛先ポート番号
- NAPTに使う空きポート番号
をNAPTテーブルに記録します。
そのパケットの送信元アドレスと送信元ポート番号をそれぞれ
- 送信元アドレス -> WAN側インターフェースに設定されたアドレス
- 送信元ポート番号 -> 先ほど決定し た空きポート番号
に書き換えます。
IPアドレス, ポート番号を書き換えているのでTCPあるいはUDPのチェックサムを再計算して書き換えます。
これでLAN側からWAN側へのNAPTは完了なのでこのパケットをWAN側インターフェースから送出します。
WAN側からLAN側への内向きパケットに対するNAPT
WAN側から応答が返ってくるときは、NAPTテーブルを参照して、先ほどこちらから送ったパケットの通信相手からの応答であることが確認できたら、先ほどと逆の変換を施してからLAN側に中継します。
NAPTエントリの破棄
10秒間通信がなければその通信に対応するNAPTエントリを削除するようにしました。
真面目な実装ならちゃんとTCP通信の状態を追ってクローズを確認すべきですね。
UDPではうまくいったものの、TCPではうまくコネクションが張れなかった
NAPTの実装を行っているとき、UDPではうまく外部との通信を中継できるのに、TCPではなぜかブラウザとサーバーとのコネクションがうまく張れませんでした。
自作ルーターにNAPT実装してUDPが疎通するようになったけどTCPがなんかうまくいかないですね
— いの (@iciclize) September 3, 2019
Windowsの方でWiresharkを使い原因を調べると、WindowsがACKを返すと、Windowsに対してRSTが送られてしまっていました。
自作ルーターに繋いだWindowsくんが外部のホストにSYN送ってSYN+ACK返ってきてACKを送ると必ずRSTが返ってきて接続0本
何がいけなかったんでしょうかね〜