Python不慣れな人が書いた Scapy メモ

以下の方が対象な感じです。まぁ私なんですけど…

* Pythonで多少コードが組める
* Scapyのインストールとかは終わっている（あまりここで細かい説明はしません）
* たまにしか使わないので、よく使い方を忘れる。

自分用のメモ的意味合いも兼ねてます。また確認は全てLinuxでやっています。

## インストールなど

公式から。

* [Download and Installation](https://scapy.readthedocs.io/en/latest/installation.html)

## パケット解析

公式では以下の「Reading PCAP files」辺りから説明があります。
https://scapy.readthedocs.io/en/latest/usage.html

すみません、にわかなんで私の場合WireSharkとの併用というのをよくやります。なので、そことの関係も考慮したサンプルにしています。また結果がどーっと表示されるかもしれません。解析データをログにしたい場合は 

```sh
python <pythonファイル名> > test.log
```

みたいにして下さい。

以下例示しますが、キャプチャファイルは適当に採集（※）し、設定値や検出条件（以下のコードでis_this_target_packet関数として書いた部分）は自分の環境に合ったものにして下さい。

※：そこらのWebページをcurlで取るとか。

### 全パケットのサマリ表示

```py

#!/usr/bin/env python
# coding: shift-jis

from scapy.all import *
from datetime import datetime

# ----->　設定値ここから
PCAP_filename = "sample.pcap"
# <-----　設定値ここまで

def parse_pcap(filename):

    packets = rdpcap(filename)

    print("----------------------------------")
    for cnt, packet in enumerate(packets, 1):
        datetime_text = datetime.fromtimestamp(packet.time).isoformat()
        print("No:", cnt, " ", datetime_text, " ", packet.summary())
    print("----------------------------------")

if __name__ == "__main__":

    parse_pcap(PCAP_filename)

    print("completed.")

```

* PCAP_filenameに解析対象のキャプチャファイル名を入れます。
* 以下のように summary が出ます。NoはWireSharkのNoと連動させてます。

```
No: 1   2019-04-14T07:09:37.526395   Ether / IP / TCP 10.0.0.11:35152 > 35.186.254.217:https PA / Raw
No: 2   2019-04-14T07:09:37.526454   Ether / IP / TCP 10.0.0.11:48508 > 183.79.249.252:https PA / Raw
No: 3   2019-04-14T07:09:37.526477   Ether / IP / TCP 10.0.0.11:43764 > 13.113.199.4:https PA / Raw
No: 4   2019-04-14T07:09:37.526497   Ether / IP / TCP 10.0.0.11:42710 > 172.217.25.202:https PA / Raw
No: 5   2019-04-14T07:09:37.535457   Ether / IP / TCP 35.186.254.217:https > 10.0.0.11:35152 A

　: 以下同じ感じで繰り返されます

```

### 特定のソケットだけ抽出（その１）

特定のデータ通信部分を抽出して、そのサマリと内容ダンプとを行います。内容についてはScapyのAPIをそのまま使っています。

```py
#!/usr/bin/env python
# coding: shift-jis

from scapy.all import *
from datetime import datetime

# ----->　設定値ここから
PCAP_filename = "sample.pcap"
SHOW_Detail = True
# <-----　設定値ここまで


def is_this_target_packet(packet):
    return TCP in packet and (packet[TCP].sport == 80 or packet[TCP].dport == 80)

def parse_pcap(filename, show_details):

    packets = rdpcap(filename)

    print("----------------------------------")
    target_cnt = 0
    for cnt, packet in enumerate(packets, 1):
        if is_this_target_packet(packet) == True:
            datetime_text = datetime.fromtimestamp(packet.time).isoformat()
            print("No:", cnt, " ", datetime_text, " ", packet.summary())
            target_cnt += 1
    print("target_cnt = ", target_cnt)
    print("----------------------------------")

    if show_details == False:
        return

    target_cnt = 0
    for cnt, packet in enumerate(packets, 1):
        if is_this_target_packet(packet) == True:
            datetime_text = datetime.fromtimestamp(packet.time).isoformat()
            print("No:", cnt, " ", datetime_text,
                  " TARGET_CNT", target_cnt, packet.summary())
            print(packet.show())
            print("----------------------------------")
            target_cnt += 1

if __name__ == "__main__":

    parse_pcap(PCAP_filename, SHOW_Detail)

    print("completed.")

```

* PCAP_filenameに解析対象のキャプチャファイル名を入れます。
* SHOW_Detailフラグは詳細データを表示するかしないかを決めます。
* is_this_target_packet関数にて対象パケットかどうかの判定を行っています。この関数の記述を変更する事で条件を変えられます。自分で取ったキャプチャのデータに合わせてください。
* 以下のように summary が出ます。NoはWireSharkのNoと連動させてます。Noを見ると分かりますが、該当パケットのみが抽出されている事が分かります。

```
----------------------------------
No: 39   2019-04-14T07:09:40.035476   Ether / IP / TCP 10.0.0.11:34892 > 216.58.197.195:http A
No: 43   2019-04-14T07:09:40.043278   Ether / IP / TCP 216.58.197.195:http > 10.0.0.11:34892 A
No: 49   2019-04-14T07:09:40.547620   Ether / IP / TCP 10.0.0.11:36018 > 117.18.237.29:http A
No: 50   2019-04-14T07:09:40.555924   Ether / IP / TCP 117.18.237.29:http > 10.0.0.11:36018 A
No: 52   2019-04-14T07:09:40.803730   Ether / IP / TCP 10.0.0.11:37494 > 23.219.39.8:http A

　: 以下同じ感じで繰り返されます


```

* SHOW_DetailフラグがTrue（デフォ）だと一覧の下に、以下のように個別解析結果がずらーっと並びます。

```
----------------------------------
No: 39   2019-04-14T07:09:40.035476  TARGET_CNT 0 Ether / IP / TCP 10.0.0.11:34892 > 216.58.197.195:http A
###[ Ethernet ]### 
  dst       = 00:3a:9d:dd:0a:54
  src       = 08:00:27:ef:c4:5d
  type      = 0x800
###[ IP ]### 
     version   = 4
     ihl       = 5
     tos       = 0x0
     len       = 52
     id        = 58129
     flags     = DF
     frag      = 0
     ttl       = 64
     proto     = tcp
     chksum    = 0xafa9
     src       = 10.0.0.11
     dst       = 216.58.197.195
     \options   \
###[ TCP ]### 
        sport     = 34892
        dport     = http
        seq       = 277691606
        ack       = 3896429984
        dataofs   = 8
        reserved  = 0
        flags     = A
        window    = 240
        chksum    = 0xa82f
        urgptr    = 0
        options   = [('NOP', None), ('NOP', None), ('Timestamp', (2762835695, 356793874))]

None

　: 以下同じ感じで繰り返されます

```

* 解析を行う際は上記の項目に対して行う感じです。例えばTTLをチェックしたければ packet[IP].ttl を見ることになりますし、Windowsサイズを見たければ packet[TCP].window の値を見ることとなります。
* 項目が分からない場合、キャプチャデータをとりまshowさせて確認するという方法もあったります（ちゃんと調べないダメな私…汗）

### 特定のソケットだけ抽出（その２）

内容を出す所を変えます。showではなく各項目を個別に表示させる方法です。

```py
#!/usr/bin/env python
# coding: shift-jis

from scapy.all import *
from datetime import datetime

# ----->　設定値ここから
PCAP_filename = "sample.pcap"
SHOW_Detail = True
# <-----　設定値ここまで


def is_this_target_packet(packet):
    return TCP in packet and (packet[TCP].sport == 80 or packet[TCP].dport == 80)

def parse_pcap(filename, show_details):

    packets = rdpcap(filename)

    print("----------------------------------")
    target_cnt = 0
    for cnt, packet in enumerate(packets, 1):
        if is_this_target_packet(packet) == True:
            datetime_text = datetime.fromtimestamp(packet.time).isoformat()
            print("No:", cnt, " ", datetime_text, " ", packet.summary())
            target_cnt += 1
    print("target_cnt = ", target_cnt)
    print("----------------------------------")

    if show_details == False:
        return

    target_cnt = 0
    for cnt, packet in enumerate(packets, 1):
        if is_this_target_packet(packet) == True:
            datetime_text = datetime.fromtimestamp(packet.time).isoformat()
            print("No:", cnt, " ", datetime_text,
                  " TARGET_CNT", target_cnt, packet.summary())
            print("IP.len        = ", packet[IP].len)
            print("TCP.sport     = ", packet[TCP].sport)
            print("TCP.dport     = ", packet[TCP].dport)
            print("TCP.seq       = ", hex(packet[TCP].seq))
            print("TCP.ack       = ", hex(packet[TCP].ack))
            print("TCP.dataofs   = ", packet[TCP].dataofs)
            print("TCP.reserved  = ", packet[TCP].reserved)
            print("TCP.flags     = ", packet[TCP].flags)
            print("TCP.window    = ", packet[TCP].window)
            print("TCP.chksum    = ", packet[TCP].chksum)
            print("TCP.urgptr    = ", packet[TCP].urgptr)
            print("TCP.options   = ", packet[TCP].options)
            if Raw in packet:
                print("[RAW] ", packet[Raw])
                print("RAW:len = ", len(packet[Raw]))
            print("----------------------------------")
            target_cnt += 1

if __name__ == "__main__":

    parse_pcap(PCAP_filename, SHOW_Detail)

    print("completed.")

```

* 設定などは前のサンプルと同じです。
* summaryは前と同じなので省略します。
* 上記のコードだと各パケットは以下のように出ます（情報量が少なくなってます）

```

----------------------------------
No: 39   2019-04-14T07:09:40.035476  TARGET_CNT 0 Ether / IP / TCP 10.0.0.11:34892 > 216.58.197.195:http A
IP.len        =  52
TCP.sport     =  34892
TCP.dport     =  80
TCP.seq       =  0x108d3cd6
TCP.ack       =  0xe83ecda0
TCP.dataofs   =  8
TCP.reserved  =  0
TCP.flags     =  A
TCP.window    =  240
TCP.chksum    =  43055
TCP.urgptr    =  0
TCP.options   =  [('NOP', None), ('NOP', None), ('Timestamp', (2762835695, 356793874))]
----------------------------------

　: 以下同じ感じで繰り返されます

```

* 前と異なり各項目を個別にprintしています。この例ではprintしていますが、例えば各項目のデータを抽出して集めたり、各値を比較するとか、項目ごとに分析が出来そうだなーというのが見て分かるかと思います。

## パケット出力

今度は試験パケットを出す側の話で。公式では以下の「Generating sets of packets」辺りから説明があります。
https://scapy.readthedocs.io/en/latest/usage.html

<font color="IndianRed">キャプチャファイルとしてはローカルパソコン相手に通信したキャプチャファイルを使って下さい。私は試験HTTPSサーバ（ポート4001）をcurlして作りました。インターネット先が相手だと攻撃になってしまう可能性があります！</font>

ここからはWireSharkの画面も使いながら見ていきます。図が小さいときはクリックすると元のサイズでの絵が出るです。

### キャプチャファイルから特定ソケットのパケットを送出

例えば既にキャプチャしたものと同じものをどーっと送るだけなら割に簡単です。以下のようにする事で通信が可能です。

```py

#!/usr/bin/env python
# coding: shift-jis

from scapy.all import *
from datetime import datetime

# ----->　設定値ここから
PCAP_filename = "test.pcap"
PCAP_ifname = "eth1"
# <-----　設定値ここまで


def is_this_target_packet(packet):
    return TCP in packet and packet[TCP].dport == 4001

def pcap_resend(filename):

    packets = rdpcap(filename)

    target_cnt = 0
    for cnt, packet in enumerate(packets, 1):
        if is_this_target_packet(packet) == True:

            # パケット加工
            #packet[IP].dst = "255.255.255.255"

            # 送信
            sendp(packet, iface=PCAP_ifname)  # layer2に送ります。
            #send(packet, iface=PCAP_ifname) # layer3に送ります。

            datetime_text = datetime.fromtimestamp(packet.time).isoformat()
            print("No:", cnt, " ", datetime_text, " ", packet.summary())
            target_cnt += 1
            if target_cnt == 10: # とりあえず10個でやめてます。
                break
    print("target_cnt = ", target_cnt)


if __name__ == "__main__":

    pcap_resend(PCAP_filename)

    print("completed.")

```

* PCAP_filenameに解析対象のキャプチャファイル名を入れます。
* PCAP_ifnameにはパケットをどこに出すかをデバイス名で指定します。
* is_this_target_packet関数にて対象パケットかどうかの判定を行っています。この関数の記述を変えられます。自分で取ったキャプチャのデータに合わせてください。
* 10パケット送ったら打ち切るようにしています。

上記の例はsendp（レイヤ2から転送）を使った例です。HTTPS(ポート4001)のデータをひたすら送るだけという、あまり意味がない物となっていますが、UDP系のデータとかTCPでもファジング的に送り込むとか、そういう用途では使えるかもしれません。

この例では元と同じパケットが出ます。WireSharkで見るとこんな感じでした（ちなみにScapyは10.0.0.14）

![scap_out1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294469/98f98e70-ced8-60f5-d3e5-e16187fdbab6.png)


ちなみにIPを変更する事も可能です。上記の　　packet[IP].dst = "255.255.255.255"　　のコメントアウトを外すと以下のようになるです。

![scap_out2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294469/06d289eb-0901-38a2-8433-e91406de8c75.png)


単に置き換えているだけなのが分かるかと思います。で、Wiresharkにてチェックサムで怒られていると。

#### パラメータを変えて、チェックサムを再計算させたい

**2019/09/08追記です**

単に書き換えるだけでなく、相手先が確実に受け取るようにチェックサムを再計算させたい場合は以下のような感じで消すコードを追加すればScapyで勝手に計算してくれるようです。

```py

# チェックサムを再計算させるため、現在のチェックサムをクリアします。以下2行を追加
# 以下のような記述を送信前に記載すれば、IPとTCP共に再計算されるっぽい？
del packet[IP].chksum
del packet[TCP].chksum

# パケット加工（ここからは同じです）
packet[IP].dst = "255.255.255.255"

# 送信
sendp(packet, iface=PCAP_ifname)  # layer2に送ります。

```

で、以下のようにチェックサムもOKとなりますです（255.255.255.255にtcpというのもなかなかですが…）。

![cap_re_calc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294469/c346b518-69ef-5367-6bb1-7b331ab0f548.png)

#### レイヤー３からの送出

コメントアウトの場所を変更してsendpではなく、sendを使うようにしますとレイヤー３からの送出となり。もう少し色々変化します。以下のようなデータが出てきます。

![scap_out3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294469/c675ddb9-9482-5a0f-2632-c95276df05e8.png)


こんな感じでキャプチャデータを出す事が可能です。

### TCP通信をやってみる

HTTPサーバに対して

* 接続
* 無効なURLを発行して、Not foundを受信
* 切断

というシナリオをScapyで実現します。前の例とは異なりレイヤ３に投げるサンプルです。私はラズパイに立てたWordpressサーバに投げて確認しました。

### RST対策

レイヤ３から投げる関係で、Linuxの標準スタックと挙動がぶつかる可能性があります。今回の例だとLinux様のTCPが勝手にRSTを出してしまうという事があります（※）。この場合はiptablesを使って以下のようにRSTを出さないようにします。sudoでやりましょう。

※：まぁ通常では問題のない挙動ではあるのですが。

■RSTを出さない設定に

```sh
# iptables -A OUTPUT -p tcp --tcp-flags RST RST -s <<自分のIP>> -j DROP
```

自分のIPとあるのはscapyを使っているパソコンのIPを指しています。

■元に戻す

実験が終わったら元に戻しましょう。こちらは以下のサンプル等を使った後で操作する感じです。

まずはリストを確認します。

```sh
# iptables -L --line-numbers
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination
1    DROP       tcp  --  kali                 anywhere             tcp flags:RST/RST
```

上のようにOUTPUTのどこかにいるはずです（例では１）。で、次の感じで削除します。

```sh
# iptables -D OUTPUT 1
```

### サンプルコード


```py
#!/usr/bin/env python
# coding: shift-jis

from scapy.all import *
from datetime import datetime
import random
import time

# ----->　設定値ここから
PCAP_http_ip = "10.0.0.8"
PCAP_http_port = 80
# <-----　設定値ここまで


def send_only(text, packet):
    """パケット送信のみ行う。送信のサマリも表示。
    　 　 レイヤ３に対して

    Parameters
    ----------
    text : str
        サマリの時に付与したい文字列
    packet : scapy.layers.inet.IP
        送信パケット
    """
    print("----> SEND_ONLY:", text)
    print(packet.summary())
    send(packet)


def send_recv(text, send_packet, tmout=None):
    """パケット送信と受信をセットで行う。送信のサマリ、受信パケットの詳細も表示。
    　 　 レイヤ３に対して

    Parameters
    ----------
    text : str
        サマリ、詳細表示の時に付与したい文字列
    packet : scapy.layers.inet.IP
        送信パケット
    tmout : int
        タイムアウト（秒）。Noneなら無制限

    Returns
    ---------
    recv_packet : scapy.layers.inet.IP
        受信パケット
    raw_len : int
        payloadがある場合、その長さ

    """
    print("----> SEND:", text)
    print(send_packet.summary())
    if tmout == None:
        recv_packet = sr1(send_packet)
    else:
        recv_packet = sr1(send_packet, timeout=tmout)
    print("<---- RECV:", text)
    raw_len = 0
    if recv_packet != None:
        print(recv_packet.show())
        if Raw in recv_packet:
            raw_len = len(recv_packet[Raw])
    else:
        print("TIMEOUT!")

    return recv_packet, raw_len


def sniff_one_packet(text, target_ip):
    """１パケットだけ受信。パケットの詳細も表示。

    Parameters
    ----------
    text : str
        詳細表示の時に付与したい文字列
    target_ip :　str 
        パケットを待つ相手

    Returns
    ---------
    recv_packet : scapy.layers.inet.IP
        受信パケット
    raw_len : int
        payloadがある場合、その長さ

    """
    print("<---- SNIFF:", text)
    raw_len = 0
    s = sniff(filter="tcp and ip src host " +
              target_ip, count=1)  # 1packet決め打ち
    recv_packet = s[0]
    print(recv_packet.show())
    if Raw in recv_packet:
        raw_len = len(recv_packet[Raw].load)
        print ("raw_len = ", raw_len)
    return recv_packet, raw_len


def http_send_test():
    """HTTP試験の本体"""

    time_out = 5  # 秒

    # IP/TCP パケット作成
    ip = IP(dst=PCAP_http_ip)
    random_port = int(random.uniform(32768.0, 63353.00))
    tcp = TCP(sport=random_port, dport=PCAP_http_port)

    # SYNを投げて、SYNACK（になるはず）を受け取る
    tcp.flags = "S"
    tcp.seq = 1000
    send_packet = ip/tcp
    recv_packet, _ = send_recv("SYN->ACK", send_packet)

    # ACKを投げる（3way-handshakeのラスト）
    tcp.seq += 1
    tcp.ack = recv_packet[TCP].seq + 1
    tcp.flags = "A"
    send_packet = ip/tcp
    send_only("3WAY_ACK", send_packet)

    # GETを投げて、応答を受け取る
    # Not Foundで受けるイメージ（短いデータを期待）
    http = "GET /hoge HTTP/1.1\r\nHost:" + PCAP_http_ip + \
        "\r\nUser-Agent: scapy/0.0\r\nAccept: */*\r\n\r\n"
    send_packet = ip/tcp/http
    recv_packet, raw_len = send_recv("GET", send_packet, time_out)

    # 相手から実体のないACKが来た場合は、実体が1個来るまで待つ
    if raw_len == 0:
        recv_packet, raw_len = sniff_one_packet("GET BODY", PCAP_http_ip)

    # FIN/ACKを投げ、パケットを受信（FIN/ACKが来ることを想定）
    tcp.seq = recv_packet[TCP].ack
    tcp.ack = recv_packet[TCP].seq + raw_len
    tcp.flags = "FA"
    send_packet = ip/tcp
    recv_packet,  _ = send_recv("FIN-ACK", send_packet, time_out)

    # ACKを投げる（3way-handshakeのラスト）
    tcp.seq = recv_packet[TCP].ack
    tcp.ack = recv_packet[TCP].seq + 1
    tcp.flags = "A"
    send_packet = ip/tcp
    send_only("3WAY_ACK", send_packet)


if __name__ == "__main__":

    http_send_test()
    print("completed.")
```

Python不慣れな老害が書き殴ったので非常に汚いコードになってますですごめんなさい…

関数的にはhttp_send_test関数から見て後は追っていけばよいかなと思います。以下に各関数の概要をば。

| 関数名 | 何してるか |
|:-----------|:------------|
| send_only  | 1パケット送信するだけ |
| send_recv | 1パケット送信後、1パケット受信して内容表示 |
| sniff_one_packet | 1パケット受信して内容表示 |
| http_send_test | HTTP通信のメインです |

http_send_test関数を見ると分かりますが、必要なTCP/IP系の操作して投げているだけな事が分かります。あとはScapy様がよしなにしてくれてるみたいです凄いです。

ちなみに受信パケットの中身ですがはpayloadのサイズ以外は見ていません。もしも解析とか必要であればrecv_packetの中を適時チェックする感じとなりますでしょうか。

私の環境では以下の感じでログが出ました。

```
.*.**.----> SEND: SYN->ACK
IP / TCP 10.0.0.14:55884 > 10.0.0.8:http S
Begin emission:
Finished sending 1 packets.

Received 2 packets, got 1 answers, remaining 0 packets
<---- RECV: SYN->ACK
###[ IP ]### 
  version   = 4
  ihl       = 5
  tos       = 0x0
  len       = 44
  id        = 0
  flags     = DF
  frag      = 0
  ttl       = 64
  proto     = tcp
  chksum    = 0x26b7
  src       = 10.0.0.8
  dst       = 10.0.0.14
  \options   \
###[ TCP ]### 
     sport     = http
     dport     = 55884
     seq       = 3602122109
     ack       = 1001
     dataofs   = 6
     reserved  = 0
     flags     = SA
     window    = 29200
     chksum    = 0x5739
     urgptr    = 0
     options   = [('MSS', 1460)]
###[ Padding ]### 
        load      = '\x00\x00'

None
----> SEND_ONLY: 3WAY_ACK
IP / TCP 10.0.0.14:55884 > 10.0.0.8:http A

Sent 1 packets.
----> SEND: GET
IP / TCP 10.0.0.14:55884 > 10.0.0.8:http A / Raw
Begin emission:
Finished sending 1 packets.

Received 1 packets, got 1 answers, remaining 0 packets
<---- RECV: GET
###[ IP ]### 
  version   = 4
  ihl       = 5
  tos       = 0x0
  len       = 40
  id        = 60835
  flags     = DF
  frag      = 0
  ttl       = 64
  proto     = tcp
  chksum    = 0x3917
  src       = 10.0.0.8
  dst       = 10.0.0.14
  \options   \
###[ TCP ]### 
     sport     = http
     dport     = 55884
     seq       = 3602122110
     ack       = 1074
     dataofs   = 5
     reserved  = 0
     flags     = A
     window    = 29200
     chksum    = 0x6ead
     urgptr    = 0
     options   = []
###[ Padding ]### 
        load      = '\x00\x00\x00\x00\x00\x00'

None
<---- SNIFF: GET BODY
###[ Ethernet ]### 
  dst       = 08:00:27:4c:7d:d3
  src       = b8:27:eb:e0:64:dc
  type      = 0x800
###[ IP ]### 
     version   = 4
     ihl       = 5
     tos       = 0x0
     len       = 481
     id        = 60837
     flags     = DF
     frag      = 0
     ttl       = 64
     proto     = tcp
     chksum    = 0x375c
     src       = 10.0.0.8
     dst       = 10.0.0.14
     \options   \
###[ TCP ]### 
        sport     = http
        dport     = 55884
        seq       = 3602122110
        ack       = 1074
        dataofs   = 5
        reserved  = 0
        flags     = PA
        window    = 29200
        chksum    = 0xcc7e
        urgptr    = 0
        options   = []
###[ Raw ]### 
           load      = 'HTTP/1.1 404 Not Found\r\nDate: Mon, 15 Jul 2019 09:06:02 GMT\r\nServer: Apache/2.4.25 (Raspbian)\r\nContent-Length: 278\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">\n<html><head>\n<title>404 Not Found</title>\n</head><body>\n<h1>Not Found</h1>\n<p>The requested URL /hoge was not found on this server.</p>\n<hr>\n<address>Apache/2.4.25 (Raspbian) Server at 10.0.0.8 Port 80</address>\n</body></html>\n'

None
raw_len =  441
----> SEND: FIN-ACK
IP / TCP 10.0.0.14:55884 > 10.0.0.8:http FA
Begin emission:
Finished sending 1 packets.

Received 1 packets, got 1 answers, remaining 0 packets
<---- RECV: FIN-ACK
###[ IP ]### 
  version   = 4
  ihl       = 5
  tos       = 0x0
  len       = 40
  id        = 60838
  flags     = DF
  frag      = 0
  ttl       = 64
  proto     = tcp
  chksum    = 0x3914
  src       = 10.0.0.8
  dst       = 10.0.0.14
  \options   \
###[ TCP ]### 
     sport     = http
     dport     = 55884
     seq       = 3602122551
     ack       = 1075
     dataofs   = 5
     reserved  = 0
     flags     = FA
     window    = 29200
     chksum    = 0x6cf2
     urgptr    = 0
     options   = []
###[ Padding ]### 
        load      = '\x00\x00\x00\x00\x00\x00'

None
----> SEND_ONLY: 3WAY_ACK
IP / TCP 10.0.0.14:55884 > 10.0.0.8:http A

Sent 1 packets.
completed.

```

WireSharkで見るとこんな感じです。

![scap_out4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294469/f4e481c3-8133-e12b-6d5e-b3537204dce7.png)

### PS

リアルタイムにとなると厳しいかもしれませんが、とにかく投げて受けての試験をするのが割に簡単に出来るように思います。またEther系までやってガンガン介入したい場合は、レイヤ２から投げるとかの工夫が必要なのかもしれませんですね。

## ちょっと宣伝

つっても私が儲かるとかでは全くありませんが（汗

Scapyでpcap解析してエクセルに出すサンプルとかGitHubに公開してます。まーもし良かったらご参考まで。

https://github.com/khamada611/scapy_to_openpyx_sample

## 参考

以下のサイトを参考にさせていただきました。皆様ありがとうございます。

* 公式です

    https://scapy.readthedocs.io/en/latest/usage.html

* TCPの出し方について、非常に参考になりました

    https://qiita.com/akakou/items/eaffe038feeb66f2cf8a

* ScapyのAPIなどについて、以下が参考になりました（戻り値の型式とか）

    http://python.zombie-hunting-club.com/entry/2018/04/18/112124
    
    https://github.com/secdev/scapy/blob/master/scapy/sendrecv.py


* RSTの件はここを参考に対策しました

    https://stackoverflow.com/questions/9058052/unwanted-rst-tcp-packet-with-scapy

* チェックサムの再計算はここを参考にしました。

    https://stackoverflow.com/questions/5953371/how-to-calculate-a-packet-checksum-without-sending-it

以上です。（いるかどうかは分からないですが）ここまで読んで下さりありがとうございました。