53
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ScapyによるTCP通信

Last updated at Posted at 2018-09-01

はじめに

現在に至るまでの世界中の人々の努力により、世の中には非常に便利で協力なツールが溢れている。その恩恵を享受することで、我々は自分のプロジェクトに専念することができる。例えば何らかの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である。管理者権限として実行することで、NICからパケットを直接取得でき

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

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

Colasoft Packet Builder

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

tcpreplay

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

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

から、今回はtcpreplayは使用しない。
なお、有志の方による非公式日本語サイトが存在する。

Scapy

Scapyは、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

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

初めてScapyに触れたときは、勝手にRSTが送られる現象をバグと認識してしまうことがあるが、極めて当たり前の動作をしているだけである。

Scapyに限らずプログラムからTCP通信を実現するには、OSがRSTを送るのをブロックするしかない。同じことに悩んでいた人は、arp spoofingを使った方法日本語版)(ARPプロトコルの応答を偽装して、ネットワーク上でなりすましを行う方法)で回避したようだが、結局arpspoofでなりすましされる端末が必要になる。今回は余分な端末もないので、直接RSTを阻害する方法をとる。

MacでのRST阻害方法

pf(packet filter)を利用する。Mac OSXに搭載されているpfはOpenBSD由来のファイアウォール機能で、ネットワークの操作を行える。今回はこちらのStackExchangeの投稿こちらのQiitaの投稿を参考にした。なお、192.168.179.9は今回の通信相手(Sever)である。

$ 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の投稿と、そこに記述されたコマンドを転載しておく。

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

Scapyの使い方

インストール

pipでインストールあるいはGitHubから最新版をインストール可能である。

$ pip install scapy

使い方

スーパーユーザーにて実行する。

$ sudo scapy

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

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

でもOKである。

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

例:ICMPのパケット送信

# 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コマンドとなる。

>>> 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接続を行ったら、あとは接続断するだけであり、一切のデータの通信を行わない。

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することで読み込んでいる。

$ 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
ちゃんと3hand shakeが実現できていることがわかる。今後は、今回確立したTCP通信の中で情報のやり取りを行う。

53
44
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?