ScapyでpcapファイルのTCPシーケンスを再現する
Scapyを使って、PCAP形式ファイル(以降、pcapファイル)に記録されたTCPシーケンスを再現できないか試してみました。結論としては、それなりにTCPシーケンスを再現できた、という感じでしょうか。きちんとTCPシーケンスを再現できるとは思いませんが、「同じTCPセッションを再現したい」ときの一つの方法としてメモしておきます。
ちなみに、普段コードを書いていないため、Pythonスクリプトには自信がないです(--; 間違い等がありましたら、ご指摘いただけると嬉しいです。
動作環境
TCPシーケンスを再現させた動作環境は下図の通りです。ノートPCからルータのWeb管理画面にアクセスしたときのHTTP通信をpcapファイルに保存して、それをScapyで再現しました。使用したpcapファイルをここにおいておきます。
ScapyによるTCPシーケンスの再現
まずScapyを送信するパケットを、pcapファイルから切り出します。送信元IPアドレス(192.168.0.104)を基にフィルタして、別途pcapファイルに保存します。以下のPythonスクリプトで、このpcapファイルを送信します。
- パケットからIPおよびTCPヘッダのチェックサムを削除する(チェックサムを再計算させるため)。
- iptablesにRSTパケットを破棄するルールを追加する(SANS Diary の記事*1を参照)。
- TCPシーケンスのパケットを一つずつ送信する。
- iptablesから2で追加したルールを削除する。
#!/usr/bin/env python import os from scapy.all import * packets=rdpcap("./192.168.0.1_http_src.pcap") ### 1 for i in range(len(packets)): del(packets[i][IP].chksum) del(packets[i][TCP].chksum) ### 2 rstrule="-p tcp --sport " + str(packets[0].sport) + " --tcp-flags RST RST -j DROP" os.system("iptables -A OUTPUT " + rstrule) ### 3 acknum = 0 for i in range(len(packets)): try: ### 3.1 ans,unans = srp(packets[i],multi=1,timeout=0.1) if len(packets) == i+1: break if len(ans) == 0: packets[i+1][TCP].ack = acknum else: for j in range(len(ans)): ### 3.2 if ans[j][1][TCP].flags == 18: packets[i+1][TCP].ack = ans[j][1][TCP].seq+1 acknum = packets[i+1][TCP].ack break; ### 3.3 if ans[j][1][TCP].seq >= acknum: if "Raw" in ans[j][1]: packets[i+1][TCP].ack = ans[j][1][TCP].seq+len(ans[j][1][Raw]) else: packets[i+1][TCP].ack = ans[j][1][TCP].seq acknum = packets[i+1][TCP].ack except: print traceback.format_exc(sys.exc_info()[2]) break; ### 4 os.system("iptables -D OUTPUT " + rstrule)
上記Pythonスクリプトを実行すると、次のような結果となります。ScapyによるTCPシーケンスを再現したときに改めてパケットキャプチャを実施しました。そのときのpcapファイルをここにおいておきます。いくつかのパケットもロストしていますね:(
root@root:~# python -V Python 2.6.5 root@root:~# python tcpsess_replay.py Begin emission: Finished to send 1 packets. * Received 1 packets, got 1 answers, remaining 0 packets Begin emission: Finished to send 1 packets. Received 0 packets, got 0 answers, remaining 1 packets Begin emission: Finished to send 1 packets. **** Received 4 packets, got 4 answers, remaining 0 packets (snip) Received 0 packets, got 0 answers, remaining 1 packets Begin emission: Finished to send 1 packets. Received 0 packets, got 0 answers, remaining 1 packets
おまけ:Scapyメモ
Scapyについては、下記にドキュメントがあります。
- Welcome to Scapy’s documentation! — Scapy 2.4.2-dev documentation
- Welcome to Scapy’s documentation! — Scapy 2.4.2-dev documentation
- Welcome to Scapy’s documentation! — Scapy 2.4.2-dev documentation
個人的にメモしておきたい点だけ以下にまとめました。ls(), lsc() だけ忘れなければ、それらの出力結果からドキュメントを調べればよいと思いました。
root@root:~# scapy Welcome to Scapy (2.1.0) >>> >>> ls() ARP : ARP ASN1_Packet : None BOOTP : BOOTP CookedLinux : cooked linux DHCP : DHCP options DHCP6 : DHCPv6 Generic Message) (snip) >>> lsc() arpcachepoison : Poison target's cache with (your MAC,victim's IP) couple arping : Send ARP who-has requests to determine which hosts are up bind_layers : Bind 2 layers on some specific fields' values corrupt_bits : Flip a given percentage or number of bits from a string corrupt_bytes : Corrupt a given percentage or number of bytes from a string defrag : defrag(plist) -> ([not fragmented], [defragmented], (snip) >>> ls(IP) version : BitField = (4) ihl : BitField = (None) tos : XByteField = (0) len : ShortField = (None) id : ShortField = (1) flags : FlagsField = (0) frag : BitField = (0) ttl : ByteField = (64) proto : ByteEnumField = (0) chksum : XShortField = (None) src : Emph = (None) dst : Emph = ('127.0.0.1') options : PacketListField = ([]) >>> ls(TCP) sport : ShortEnumField = (20) dport : ShortEnumField = (80) seq : IntField = (0) ack : IntField = (0) dataofs : BitField = (None) reserved : BitField = (0) flags : FlagsField = (2) window : ShortField = (8192) chksum : XShortField = (None) urgptr : ShortField = (0) options : TCPOptionsField = ({}) >>> help(rdpcap) Help on function rdpcap in module scapy.utils: rdpcap(filename, count=-1) Read a pcap file and return a packet list count: read only <count> packets