ScapyによるTCP通信

# はじめに
現在に至るまでの世界中の人々の努力により、世の中には非常に便利で協力なツールが溢れている。その恩恵を享受することで、我々は自分のプロジェクトに専念することができる。例えば何らかのWebサービスを作ろうとしたとき、サーバーフレームワークから作ろうとはしないはずである。世界には、Apacheなり、IISなり、あるいはRailsやDjangoといったツールが揃っている。
同じ理由で、通信に関する部分もフレームワークなりOSなりに任せていることが通常である。特にクラウド等の利用によるIaaSやPaaSが当たり前になっている状態において、クエリに対しての処理を記述することはあっても、「このパケットのペイロードにデータを格納して〜」とか「MACアドレスとIPの変換は〜」とか考えることはなかなかない。

しかし、何事にも例外は存在し、先人たちのツールに介入したい場合などが考えられる。例えば、自社内の独立したネットワーク上のシステムをイメージする。ネットワークに属した計算機が相互に通信を行い、一つの巨大システムを構築する。そのうちの一つの計算機が、トラブったとしよう。原因究明のためにログを漁ることはもちろんであるが、「そのシステムがトラブルを起こす前後の状況をすべて再現したい」とも考えるはずだ。そんなとき、これまでは先人たちのツールに任せていた通信を"手ずから構築・実行する"必要にかられる。

今回はそんなイレギュラーなケースを対象に、通信を行う方法を記す。なお、今回目標とするのはTCP通信の3hand shakeである。

# 手段の検討
ネットワーク上にアクセスするためには、まずはツールの選択が重要である。

## pcap
pcap(packet capture)は、ネットワーク上でスニファリング（要は盗聴）するためのAPIである。UNIX系のシステムではlibpcapとして実装されており、それをWindowsに移植したのがWinPcapである。後述のWiresharkにも組み込まれているなど、ネットワークに対してなにか行おうと考えたときのベースとなる。


## Wireshark
ネットワーク解析において非常に強力かつ有名なツールが[Wireshark](https://www.wireshark.org/)である。管理者権限として実行することで、NICからパケットを直接取得でき

- パケットの可視化を即時実行
- パケットのデコードも可能
- 特定のフィルタリングも可能
- 取得したパケットを定期的にファイル出力可能
- 出力したファイル(.pcap)を読み込んで解析することも可能

と非常に便利なツールである。とりあえず、今回は通信内容を監視するために使用する。

## Colasoft Packet Builder
[Colasoft Packet Builder](https://www.colasoft.com/jp/packet_builder/)は、パケットの生成・編集・送信することが可能なツールである。Wiresharkなどで取得したpcapファイルを読み込むことも可能で、手軽にパケットを送信するだけであれば十分な機能を有している。しかし、Windows環境でしか動かないことや、GUIを基本とした使い方であることから、大量のデータを編集して送信するなどには向かない。

## tcpreplay
[tcpreplay](https://tcpreplay.appneta.com/)はパケットのキャプチャ・編集・送信を可能なツール群である。tcpreplayを中心に、キャプチャ用のコマンドや編集用のコマンドが揃っており、ある時点での通信を再現する操作が一通り可能である。しかし、

- パケットキャプチャに関してはWiresharkが非常に強力であること
- 今回はTCP通信の3hand shakeから実行したいため、パケットの生成と送信をより直感的に行いたい
- 将来的にパケット送信タイミングの制御/再現を目指しており、tcpreplay以外のツールが必要になること

から、今回はtcpreplayは使用しない。
なお、有志の方による[非公式日本語サイト](http://otsuka752.github.io/)が存在する。

## Scapy
[Scapy](https://scapy.net/)は、Pythonによって記述されたpacket manipulation toolである。パケットのキャプチャ・生成・編集・送信が可能なツールであり、Pythonコードの中から実行することも可能なため、他の処理と合わせてPythonコード内で完結することができる。また、Pythonであることからある程度OSや環境を自由に構築することができる。
難点としては、Pythonであるがために、例えばpacket送信タイミングの時間精度が気になるところであるが、今回はこだわらないものとする。

# OSによる介入
Scapyの使い方について述べる前に、先にぶつかるであろう問題について述べておく。今回実現したいことはTCP通信を実現することであるが、TCPはOSが適切に管理している。そのため、「プログラムがTCP通信を実行」しようとしても、それは「OSから見たら異常な操作」となり、介入を受けてしまう。
その内容について解説する。

## 基本的なTCP通信
TCP通信の基本的な接続を下記に示す。
![スクリーンショット 2018-09-01 17.09.51.png](https://qiita-image-store.s3.amazonaws.com/0/93354/de578f74-deaf-bc66-8cf8-d55c71a8b4f4.png)

## OSによって介入される例
3hand shakeにて、TCP通信を結ぶときを例に説明する。説明の都合上、Client側をOSとプログラムに分けて記述する。ClientからServerにSYNを送ると、ServerはSYN-ACKを返してくる。しかし、ClientのOSは「TCP通信をかけていない」と認識しているため、SYN-ACKを受け取れず、RSTを返してTCP通信をリセットする。それに遅れてClientのプログラムがACKを返すが、すでにTCP通信はリセット済みである。

![スクリーンショット 2018-09-01 17.09.46.png](https://qiita-image-store.s3.amazonaws.com/0/93354/2fa1bb5d-d927-dc06-c80d-68a2e9fa71a8.png)


初めてScapyに触れたときは、勝手にRSTが送られる現象を[バグと認識してしまうこと](https://bitbucket.org/secdev/scapy/issues/111/unexpend-tcp-rst-packet-from-sr1)があるが、極めて当たり前の動作をしているだけである。

Scapyに限らずプログラムからTCP通信を実現するには、OSがRSTを送るのをブロックするしかない。同じことに悩んでいた人は、[arp spoofingを使った方法](https://jvns.ca/blog/2014/08/12/what-happens-if-you-write-a-tcp-stack-in-python/)（[日本語版](https://postd.cc/what-happens-if-you-write-a-tcp-stack-in-python/)）（ARPプロトコルの応答を偽装して、ネットワーク上でなりすましを行う方法）で回避したようだが、結局arpspoofでなりすましされる端末が必要になる。今回は余分な端末もないので、直接RSTを阻害する方法をとる。

## MacでのRST阻害方法
pf(packet filter)を利用する。Mac OSXに搭載されているpfはOpenBSD由来のファイアウォール機能で、ネットワークの操作を行える。今回は[こちらのStackExchangeの投稿](https://superuser.com/questions/799398/how-to-stop-sending-rst-to-specific-ip)と[こちらのQiitaの投稿](https://qiita.com/zakkied/items/dd41af4e45df635ad9d1)を参考にした。なお、192.168.179.9は今回の通信相手（Sever）である。

```text
$ sudo cp /etc/pf.conf /etc/pf.conf.disable_rst
$ sudo echo 'block drop proto tcp from any to 192.168.179.9 flags R/R' >> /etc/pf.conf.disable_rst
$ sudo pfctl -f /etc/pf.conf.disable_rst
pfctl: Use of -f option, could result in flushing of rules
present in the main ruleset added by the system at startup.
See /etc/pf.conf for further details.

No ALTQ support in kernel
ALTQ related functions disabled
$ sudo pfctl -e
No ALTQ support in kernel
ALTQ related functions disabled
pfctl: pf already enabled
```

## LinuxでのRST阻害方法
今回のClient環境がMacであったため、その検証しかしていない。Linuxの場合は、`iptables`によって対応するらしい。参考までに[stackoverflowの投稿](https://stackoverflow.com/questions/9058052/unwanted-rst-tcp-packet-with-scapy)と、そこに記述されたコマンドを転載しておく。

```text
iptables -A OUTPUT -p tcp --tcp-flags RST RST -s 192.168.1.20 -j DROP
```

# Scapyの使い方
## インストール
pipでインストールあるいは[GitHub](https://github.com/secdev/scapy)から最新版をインストール可能である。

```text
$ pip install scapy
```

## 使い方
スーパーユーザーにて実行する。

```text
$ sudo scapy
```

で、IPythonが起動し、Scapyの関数が一通り実行できる。
あるいは

```text
$ sudo python
>>> from scapy.all import *
```

でもOKである。


Scapyにおけるパケットの生成は、実際のパケット構成と似た直感的な書式で記述可能である。具体例と合わせて、示していく。

### 例：ICMPのパケット送信

```text
# 192.168.1.1へのICMPパケットの生成
# なお、こちらのIPは192.168.179.127である。
# IPのかわりに、www.google.comのようなURLでもOK
>>> pkt = IP(dst = '192.168.179.1') / ICMP()
# パケットの中身を確認
>>> pkt.show()
###[ IP ]###
  version= 4
  ihl= None
  tos= 0x0
  len= None
  id= 1
  flags=
  frag= 0
  ttl= 64
  proto= icmp
  chksum= None
  src= 192.168.179.127
  dst= 192.168.179.1
  \options\
###[ ICMP ]###
     type= echo-request
     code= 0
     chksum= None
     id= 0x0
     seq= 0x0

# パケットのsend & receive 1 packet
>>> sr1(pkt)
Begin emission:
..Finished sending 1 packets.
.*
Received 4 packets, got 1 answers, remaining 0 packets
<IP  version=4 ihl=5 tos=0x0 len=28 id=51974 flags= frag=0 ttl=64 proto=icmp chksum=0xc808 src=192.168.179.1 dst=192.168.179.127 options=[] |<ICMP  type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 |>>
>>>
```

### 例：SYNスキャン
TCPにSYNを詰めて相手先ポートに送信するSYNスキャンも、1コマンドとなる。

```text
>>> sr1(IP(dst = '192.168.179.1') / TCP(dport = 80, flags = 'S'))
Begin emission:
..Finished sending 1 packets.
.*
Received 4 packets, got 1 answers, remaining 0 packets
<IP  version=4 ihl=5 tos=0x0 len=44 id=0 flags=DF frag=0 ttl=64 proto=tcp chksum=0x52fa src=192.168.179.1 dst=192.168.179.127 options=[] |<TCP  sport=http dport=ftp_data seq=3668123710 ack=1 dataofs=6 reserved=0 flags=SA window=29200 chksum=0x42ed urgptr=0 options=[('MSS', 1460)] |>>
```

# TCP通信をScapyにて実現
繰り返しになるが、今回のゴールは3hand shakeを行ってTCP通信を確立することである。将来的にはインターフェースやポートを変えて複数の通信を管理できるようにしたいので、TCP通信自体をクラスとして実装した。

## クラスとしての実装
具体的なコードを以下に示す。今回は簡単のため、TCP接続を行ったら、あとは接続断するだけであり、一切のデータの通信を行わない。

```python
from scapy.all import *


class TCP_CONNECT:

    def __init__(self,
                 src = '127.0.0.1',
                 dst = '127.0.0.1',
                 sport = 60000,
                 dport = 60000):
        self.src = src
        self.dst = dst
        self.sport = sport
        self.dport = dport
        self.ip = IP(dst = self.dst)
        self.tcp = TCP(sport = self.sport, dport = self.dport, flags = 'S', seq = 100)


    def synchronize(self):
        ''' request for TCP connection
        '''
        # send sync & get ack
        syn = self.ip / self.tcp
        self.syn_ack = sr1(syn)
        # send sync
        self.tcp.seq += 1
        self.tcp.ack = self.syn_ack.seq + 1
        self.tcp.flags = 'A'
        ack = self.ip / self.tcp
        send(ack)
        return syn, self.syn_ack, ack


    def fin(self):
        ''' finish for TCP connection
        '''
        # send FIN packet
        self.tcp.seq
        fin = self.ip / TCP(sport = self.sport,
                            dport = self.dport,
                            flags = 'FA',
                            seq = self.syn_ack.ack,
                            ack = self.syn_ack.seq + 1)
        self.fin_ack = sr1(fin)

        # return final ACK
        lastack = self.ip / TCP(sport = self.sport,
                                dport = self.dport,
                                flags = 'A',
                                seq = self.fin_ack.ack,
                                ack = self.fin_ack.seq + 1)
        send(lastack)


    def __del__(self):
        self.fin()
```

## クラスを使ったTCP接続
上記クラスを使用して通信を実現してみた。今回、自分のIPを192.168.179.127でポートを54321、通信相手にはWeb Serverを用意し、IPを192.168.179.9でポートを80にした。
なお、上記のクラスは、```pypacian.main```をimportすることで読み込んでいる。

```text
$ sudo python
Password:
Python 3.6.1 (default, Aug 27 2017, 16:38:38)
[GCC 4.2.1 Compatible Clang 3.9.1 (tags/RELEASE_391/final)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pypacian.main import *  # クラスの読み込み
>>> tcp_connect = TCP_CONNECT(src = '192.168.179.127', dst = '192.168.179.9', sport = 54321, dport = 80)  # TCP通信クラスの定義
>>> tcp_connect.synchronize()  # 3hand shakeを実行
Begin emission:
...........................Finished sending 1 packets.
........................................................................................................................................................*
Received 180 packets, got 1 answers, remaining 0 packets
.
Sent 1 packets.
(<IP  frag=0 proto=tcp dst=192.168.179.9 |<TCP  sport=54321 dport=http seq=100 flags=S |>>, <IP  version=4 ihl=5 tos=0x0 len=44 id=0 flags=DF frag=0 ttl=64 proto=tcp chksum=0x52f2 src=192.168.179.9 dst=192.168.179.127 options=[] |<TCP  sport=http dport=54321 seq=1413997991 ack=101 dataofs=6 reserved=0 flags=SA window=29200 chksum=0x2f56 urgptr=0 options=[('MSS', 1460)] |<Padding  load='\x00\x00' |>>>, <IP  frag=0 proto=tcp dst=192.168.179.9 |<TCP  sport=54321 dport=http seq=101 ack=1413997992 flags=A |>>)
>>> tcp_connect.fin()
Begin emission:
..................................................................Finished sending 1 packets.
.................................................*
Received 116 packets, got 1 answers, remaining 0 packets
.
Sent 1 packets.
>>>
```
このあと、クラスのデコンストラクタの動きがおかしかった気がするが、最低限のTCP通信を確立することができた。

最後に、このときのWiresharkの様子を記しておく。
![スクリーンショット 2018-08-26 22.18.31.png](https://qiita-image-store.s3.amazonaws.com/0/93354/5737770b-3f2f-d379-89af-480a95f800c8.png)
ちゃんと3hand shakeが実現できていることがわかる。今後は、今回確立したTCP通信の中で情報のやり取りを行う。