PythonでSYNパケットを送ってみる

#SYNパケットを送るPythonスクリプト
TCPでコネクションを確立する際に送られるSYNパケットを送るPythonスクリプトを書いたのでメモです。


```python:syn.py
import socket, struct
from ctypes import *


SRC_IP = "127.0.0.1"
DST_IP = "127.0.0.1"

SRC_PORT = 1234
DST_PORT = 5678

WINDOWSIZE = 512


class tcphdr(Structure):
    _fields_ = [
            ("src_port", c_uint16),
            ("dst_port", c_uint16),
            ("seq", c_uint32),
            ("ack_seq", c_uint32),
            ("res1", c_uint16, 4),#little endian. from here 
            ("doff", c_uint16, 4),
            ("fin", c_uint16, 1),
            ("syn", c_uint16, 1),
            ("rst", c_uint16, 1),
            ("psh", c_uint16, 1),
            ("ack", c_uint16, 1),
            ("urg", c_uint16, 1),
            ("ece", c_uint16, 1),
            ("cwr", c_uint16, 1),#to here

            #("doff", c_uint16, 4),#big endian. from here
            #("res1", c_uint16, 4),
            #("cwr", c_uint16, 1),
            #("ece", c_uint16, 1),
            #("urg", c_uint16, 1),
            #("ack", c_uint16, 1),
            #("psh", c_uint16, 1),
            #("rst", c_uint16, 1),
            #("syn", c_uint16, 1),
            #("fin", c_uint16, 1),#to here

            ("window", c_uint16),
            ("check", c_uint16),
            ("urg_ptr", c_uint16),
            ]

    def pack(self):
        return buffer(self)[:]


class dummy_iphdr(Structure):
    _fields_ = [
            ("src_ip", c_uint32),
            ("dst_ip", c_uint32),
            ("pad", c_uint8),
            ("protocol", c_uint8),
            ("len", c_uint16),
            ]

    def pack(self):
        return buffer(self)[:]


def ip2int(ip_addr):
    #return struct.unpack("!I", socket.inet_aton(ip_addr))[0]
    #inet_aton returns ip_addr in network byte order. so the '!' above is not required.
    return struct.unpack("I", socket.inet_aton(ip_addr))[0]


def calc_checksum(TCPheader):
    IPheader = dummy_iphdr()
    IPheader.src_ip = ip2int(SRC_IP)
    IPheader.dst_ip = ip2int(DST_IP)
    IPheader.pad = 0
    IPheader.protocol = 6
    IPheader.len = socket.htons(20)
    IPheader_packed = IPheader.pack()

    checksum = 0

    for i in xrange(len(IPheader_packed) / 2):
        temp = struct.unpack("H", IPheader_packed[:2])[0]
        checksum += temp
        IPheader_packed = IPheader_packed[2:]

    TCPheader_packed = TCPheader.pack()

    for i in xrange(len(TCPheader_packed) / 2):
        temp = struct.unpack("H", TCPheader_packed[:2])[0]
        checksum += temp
        TCPheader_packed = TCPheader_packed[2:]

    carry = (checksum >> 16) & 0xffff

    checksum = ~((checksum & 0xffff) + carry)

    return checksum


def build_tcp_syn_packet():
    TCPheader = tcphdr()

    TCPheader.src_port = socket.htons(SRC_PORT)
    TCPheader.dst_port = socket.htons(DST_PORT)

    TCPheader.seq = 1#a random number mihgt be better
    TCPheader.doff = len(TCPheader.pack()) / 4

    TCPheader.fin = 0
    TCPheader.syn = 1
    TCPheader.rst = 0
    TCPheader.psh = 0
    TCPheader.ack = 0
    TCPheader.urg = 0
    TCPheader.ece = 0
    TCPheader.cwr = 0

    TCPheader.check = 0
    TCPheader.window = socket.htons(WINDOWSIZE)
    TCPheader.urg_ptr = 0

    TCPheader.check = calc_checksum(TCPheader)

    return TCPheader


def send_syn_packet():
    syn_packet = build_tcp_syn_packet().pack()

    sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)

    sock.sendto(syn_packet, (DST_IP, DST_PORT))

    sock.close()


def main():
    send_syn_packet()


if __name__ == "__main__":
    main()

```

#ハマったところ
socketはソケットタイプにSOCK_STREAMを指定しているときはプロトコル番号に0を指定しても自動でIPパケットのプロトコル番号をTCPに設定してくれますが、SOCK_RAWを指定しているときは明示的にIPPROTO_TCPを指定する必要があるみたいです。

チェックサムの計算をする部分でハマりました。inet_atonはネットワークのバイトオーダを返すのに、それをもう一度unpackでネットワークのバイトオーダに直していたので、上手く行きませんでした(結局ネイティブのバイトオーダになっていた?)。

#最後に
書き終わった後にもっとかっこ良いPythonスクリプトみつけました(http://www.binarytides.com/python-syn-flood-program-raw-sockets-linux/ (英語))。

#参考
http://telracsmoratori.blog.fc2.com/blog-entry-116.html
https://ja.wikipedia.org/wiki/Transmission_Control_Protocol