scapyで不正なtcp optionのパケットを生成する


ネットワーク機器などの検証のために通常は生成されない不正なパケットが必要なケースがあります。
そんな不正なパケットを生成するときに非常に重宝するpythonのライブラリがscapyです。IPやTCPで不正な長さの値の入ったパケットなどが簡単に作れます。
今回はTCPオプションの長さが不正なパケットを作成しようと試みましたが、オプションの長さの値を自動的に計算してパケットを作成するようになっていました。

TCPオプションフィールドは type-length-value の形式になっています。
しかしscapyの指定するオプションは以下の通りで type とvalueしか指定できません。

pkt=IP()/TCP(dport=80, flags="S", options=[("MSS",1460)]) 

オプションは TCPOptionsField というフィールドになっていて、値に何をいれても長さを挿入してきます。

ls(TCP())
sport      : ShortEnumField                      = 20              (20)
dport      : ShortEnumField                      = 80              (80)
seq        : IntField                            = 0               (0)
ack        : IntField                            = 0               (0)
dataofs    : BitField (4 bits)                   = None            (None)
reserved   : BitField (3 bits)                   = 0               (0)
flags      : FlagsField (9 bits)                 = 2               (2)
window     : ShortField                          = 8192            (8192)
chksum     : XShortField                         = None            (None)
urgptr     : ShortField                          = 0               (0)
options    : TCPOptionsField                     = {}              ({})

ここでもちろんTCPヘッダをバイナリで書いていく方法もあるのですが、TCPのチェックサムの計算はIPヘッダも入るので面倒になり、scapyを使うメリットがなくなってしまいます。

そこでTCPのクラスを継承したクラスを作成して上記のoptionsを上書してみました。
TCPのクラスはlayersの下のinet.pyにありました。

inet.py
class TCP(Packet):
    name = "TCP"
    fields_desc = [ ShortEnumField("sport", 20, TCP_SERVICES),
                    ShortEnumField("dport", 80, TCP_SERVICES),
                    IntField("seq", 0),
                    IntField("ack", 0),
                    BitField("dataofs", None, 4),
                    BitField("reserved", 0, 3),
                    FlagsField("flags", 0x2, 9, "FSRPAUECN"),
                    ShortField("window", 8192),
                    XShortField("chksum", None),
                    ShortField("urgptr", 0),
                    TCPOptionsField("options", []) ]

上記のTCPOptionsFieldをべつの自由にバイナリデータが書けるやつに書き換えてしまえばいけるかもしれません。他のレイヤをみていたらStrFixedLenFieldというのがあり、これを試してみることにしました。

MYTCPを定義してTCPクラスを継承、optionsを上書きします。

from scapy.all import *
class MYTCP(TCP):
    fields_desc = [ ShortEnumField("sport", 20, TCP_SERVICES),
                    ShortEnumField("dport", 80, TCP_SERVICES),
                    IntField("seq", 0),
                    IntField("ack", 0),
                    BitField("dataofs", None, 4),
                    BitField("reserved", 0, 3),
                    FlagsField("flags", 0x2, 9, "FSRPAUECN"),
                    ShortField("window", 8192),
                    XShortField("chksum", None),
                    ShortField("urgptr", 0),
                    StrFixedLenField("options", "\x00\x00\x00\x00",4) ]

実際にパケットをつくってみます。

pkt=IP(dst="3.3.3.3")/MYTCP(dport=80,flags="S",options="\x99\x98\x97\x96")/"AAA"

次にパケットが正常に作成できているかみてみます。
checksum 0xaddbの次がurgptr で\x00\x00 
その次がoptionで今回指定した\x99\x98\x97\x96となっていて不正なフィールドを作成できました。

pkt.show2()

###[ IP ]###
  version   = 4
  ihl       = 5
  tos       = 0x0
  len       = 47
  id        = 1
  flags     = 
  frag      = 0
  ttl       = 64
  proto     = tcp
  chksum    = 0x5c9d
  src       = 10.255.13.39
  dst       = 3.3.3.3
  \options   \
###[ TCP ]###
     sport     = ftp_data
     dport     = http
     seq       = 0
     ack       = 0
     dataofs   = 6
     reserved  = 0
     flags     = S
     window    = 8192
     chksum    = 0xaddb
     urgptr    = 0
     options   = [(153, b'\x97\x96')]
###[ Raw ]###
        load      = 'AAA'

print(bytes(pkt))

b"E\x00\x00/\x00\x01\x00\x00@\x06\\\x9d\n\xff\r'\x03\x03\x03\x03\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00`\x02 \x00\xad\xdb\x00\x00\x99\x98\x97\x96AAA

パケットを送ると不正なパケットがきちんと送信されました。

send(pkt)