數(shù)據(jù)包處理利器——Scapy基礎(chǔ)知識(shí)
本文轉(zhuǎn)載自微信公眾號(hào)「運(yùn)維開發(fā)故事」,作者wanger。轉(zhuǎn)載本文請(qǐng)聯(lián)系運(yùn)維開發(fā)故事公眾號(hào)。
什么 是scapy
Scapy是功能強(qiáng)大的交互式數(shù)據(jù)包處理程序。它能夠偽造或解碼各種協(xié)議的數(shù)據(jù)包,在線發(fā)送,捕獲,匹配請(qǐng)求和響應(yīng)等。它可以輕松處理大多數(shù)經(jīng)典任務(wù),例如掃描,跟蹤路由,探測(cè),單元測(cè)試,攻擊或網(wǎng)絡(luò)發(fā)現(xiàn),它可以代替hping,arpspoof,arp-sk,arping,p0f甚至Nmap,tcpdump和tshark的某些部分。。它在其他工具無法處理的許多其他特定任務(wù)上也表現(xiàn)出色,例如發(fā)送無效幀,組合技術(shù)(VLAN跳變+ ARP緩存中毒,WEP加密通道上的VOIP解碼等等)
安裝scapy
直接pip安裝即可,我使用的是python3
- pip3 install scapy
 
scapy基本使用
輸入scapy回車進(jìn)入scapy的shell 可以使用ls()來查看scapy支持的協(xié)議
使用lsc()查看scapy支持的函數(shù)
還可以使用ls()獲取協(xié)議包含的參數(shù)
發(fā)送和接收數(shù)據(jù)包
send
- 在第3層發(fā)送數(shù)據(jù)包(Scapy創(chuàng)建第2層標(biāo)頭),不接收任何數(shù)據(jù)包。
- loop 參數(shù)默認(rèn)為0,如果它的值不是0,那么數(shù)據(jù)包將一直循環(huán)發(fā)送,直到按CTRL-C為止。
 - count 可用于設(shè)置要發(fā)送的數(shù)據(jù)包的確切數(shù)量。
 - inter 可用于設(shè)置每個(gè)數(shù)據(jù)包之間的秒數(shù)。
 
- >>> send(IP(dst='8.8.8.8')/TCP(dport=53, flags='S'))
 - .
 - Sent 1 packets.
 - >>>
 - >>> send(IP(dst='8.8.8.8')/TCP(dport=53, flags='S'), count=10)
 - ..........
 - Sent 10 packets.
 - >>>
 - >>> send(IP(dst='8.8.8.8')/TCP(dport=53, flags='S'), loop=1)
 - ......................... [... snipped ...]
 - Sent 1503 packets.
 
sendp
- 與send()相同,但在第2層發(fā)送數(shù)據(jù)包(必須提供第2層標(biāo)頭),不接收任何數(shù)據(jù)包。
 - 使用iface到設(shè)置界面上發(fā)送數(shù)據(jù)包。(如果未設(shè)置,將使用conf.iface的值)
 
- >>> sendp(Ether()/IP(dst="1.2.3.4",ttl=(1,4)), iface="eth0")
 - ....
 - Sent 4 packets.
 - >>> sendp("I’m travelling on Ethernet", iface="eth0", loop=1, inter=0.2)
 - >>> sendp(rdpcap("/tmp/pcapfile")) # tcpreplay
 - ...........
 - Sent 11 packets.
 
sr
- 發(fā)送數(shù)據(jù)包并接收響應(yīng)。
 - sr()返回兩個(gè)列表,第一個(gè)列表包含響應(yīng)的,第二個(gè)列表包含未響應(yīng)的。
 
- >>> sr(IP(dst="60.205.177.168")/TCP(dport=[21,22,23]))
 - Begin emission:
 - Finished sending 3 packets.
 - ...**...............................^C
 - Received 36 packets, got 2 answers, remaining 1 packets
 - (<Results: TCP:2 UDP:0 ICMP:0 Other:0>,
 - <Unanswered: TCP:1 UDP:0 ICMP:0 Other:0>)
 - >>> ans,unans=_
 - >>> unans.summary()
 - IP / TCP 172.17.51.80:ftp_data > 60.205.177.168:telnet S
 - >>> ans[0]
 - (<IP frag=0 proto=tcp dst=60.205.177.168 |<TCP dport=ftp |>>,
 - <IP version=4 ihl=5 tos=0x0 len=40 id=53978 flags=DF frag=0 ttl=64 proto=tcp chksum=0x9a1e src=60.205.177.168 dst=172.17.51.80 |<TCP sport=ftp dport=ftp_data seq=0 ack=1 dataofs=5 reserved=0 flags=RA window=0 chksum=0xe1cf urgptr=0 |>>)
 - >>> ans[0][0]
 - <IP frag=0 proto=tcp dst=60.205.177.168 |<TCP dport=ftp |>>
 
sr1
- 發(fā)送所有數(shù)據(jù)包并僅記錄第一個(gè)響應(yīng)。
 
- >>> p=sr1(IP(dst="www.baidu.com")/ICMP()/"asdqwe")
 - Begin emission:
 - Finished sending 1 packets.
 - .*
 - Received 2 packets, got 1 answers, remaining 0 packets
 
srloop
- 循環(huán)發(fā)送,接收響應(yīng)并顯示響應(yīng)。
 - 該函數(shù)返回幾個(gè)數(shù)據(jù)包和響應(yīng),以及未響應(yīng)的。
 
- >>> packet = IP(dst='60.205.177.168')/ICMP()
 - >>> srloop(packet)
 - RECV 1: IP / ICMP 60.205.177.168 > 172.17.51.80 echo-reply 0
 - RECV 1: IP / ICMP 60.205.177.168 > 172.17.51.80 echo-reply 0
 - RECV 1: IP / ICMP 60.205.177.168 > 172.17.51.80 echo-reply 0
 - RECV 1: IP / ICMP 60.205.177.168 > 172.17.51.80 echo-reply 0
 - ^C
 - Sent 4 packets, received 4 packets. 100.0% hits.
 - (<Results: TCP:0 UDP:0 ICMP:9 Other:0>,
 - <PacketList: TCP:0 UDP:0 ICMP:0 Other:0>)
 
使用Scapy創(chuàng)建數(shù)據(jù)包
- Scapy數(shù)據(jù)包的創(chuàng)建與網(wǎng)絡(luò)中的分層方法一致。
 - 數(shù)據(jù)包的基本構(gòu)建塊是一層,而整個(gè)數(shù)據(jù)包則是通過將各個(gè)層堆疊在一起而構(gòu)建的。
 - scapy通過在TCP / IP的不同層上為每個(gè)協(xié)議定義數(shù)據(jù)包頭,然后按順序堆疊這些層,來構(gòu)造數(shù)據(jù)包。
 
在一行中創(chuàng)建數(shù)據(jù)包
- >>> packet = Ether()/IP(dst='8.8.8.8')/TCP(dport=53,flags='S')
 
分別創(chuàng)建每個(gè)圖層并使用'/'運(yùn)算符將它們堆疊
- >>> l2 = Ether()
 - >>> l3 = IP(dst='8.8.8.8/30')
 - >>> l4 = TCP(dport=53, flags = 'S')
 - >>> packet = l2/l3/l4
 
Scapy IP表示法
Scapy接受普通的IP表示法,CIDR表示法,主機(jī)名。
- >>> packet = IP(dst = '8.8.8.8')
 - >>> packet = IP(dst = 'scanme.nmap.org')
 - >>> packet = IP(dst = '8.8.8.8/30')
 - >>> [a for a in packet]
 - [<IP dst=8.8.8.8 |>, <IP dst=8.8.8.9 |>, <IP dst=8.8.8.10 |>, <IP dst=8.8.8.11 |>]
 - >>> packet = IP(dst = 'egadz.metasploit.com/30')
 
創(chuàng)建一組數(shù)據(jù)包
我們可以使用Scapy創(chuàng)建一組數(shù)據(jù)包
- >>> pkts = IP(ttl=[1,3,5,(7,10)])/TCP()
 - >>> [pkt for pkt in pkts]
 - [<IP frag=0 ttl=1 proto=tcp |<TCP |>>,
 - <IP frag=0 ttl=3 proto=tcp |<TCP |>>,
 - <IP frag=0 ttl=5 proto=tcp |<TCP |>>,
 - <IP frag=0 ttl=7 proto=tcp |<TCP |>>,
 - <IP frag=0 ttl=8 proto=tcp |<TCP |>>,
 - <IP frag=0 ttl=9 proto=tcp |<TCP |>>,
 - <IP frag=0 ttl=10 proto=tcp |<TCP |>>]
 - >>> packet=IP(dst="192.168.*.1-10")/TCP(dport=(0,100))
 - >>> [a for a in packet]
 - [<IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=0 |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=tcpmux |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=compressnet |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=3 |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=4 |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=rje |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=6 |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=echo |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=8 |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=discard |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=10 |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=systat |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=12 |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=daytime |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=14 |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=netstat |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=16 |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=qotd |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=msp |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=chargen |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=ftp_data |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=ftp |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=ssh |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=telnet |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=lmtp |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=smtp |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=26 |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=nsw_fe |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=28 |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=msg_icp |>>,
 - <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=30 |>>,
 - ...
 
檢查數(shù)據(jù)包
獲取數(shù)據(jù)包的詳細(xì)說明以及數(shù)據(jù)類型
- >>> packet = IP()/TCP()
 - >>> ls(packet)
 - version : BitField = 4 (4)
 - ihl : BitField = None (None)
 - tos : XByteField = 0 (0)
 - len : ShortField = None (None)
 - id : ShortField = 1 (1)
 - flags : FlagsField = 0 (0)
 - frag : BitField = 0 (0)
 - ttl : ByteField = 64 (64)
 - proto : ByteEnumField = 6 (0)
 - chksum : XShortField = None (None)
 - src : Emph = '127.0.0.1' (None)
 - dst : Emph = '127.0.0.1' ('127.0.0.1')
 - options : PacketListField = [] ([])
 - [-- snipped --]
 
show
顯示詳細(xì)的包頭
- >>> packet.show()
 - ###[ IP ]###
 - version= 4
 - ihl= None
 - tos= 0x0
 - len= None
 - id= 1
 - flags=
 - frag= 0
 - ttl= 64
 - proto= tcp
 - chksum= None
 - src= 127.0.0.1
 - dst= 127.0.0.1
 - \options\
 - ###[ TCP ]###
 - sport= ftp_data
 - dport= http
 - seq= 0
 - ack= 0
 - dataofs= None
 - reserved= 0
 - flags= S
 - window= 8192
 - chksum= None
 - urgptr= 0
 - options= []
 
show2
與show()類似,但可以組裝數(shù)據(jù)包并計(jì)算校驗(yàn)和和IHL(報(bào)頭長度,最小值是5)。
- >>> packet.show2()
 - ###[ IP ]###
 - version= 4
 - ihl= 5
 - tos= 0x0
 - len= 40
 - id= 1
 - flags=
 - frag= 0
 - ttl= 64
 - proto= tcp
 - chksum= 0x7ccd
 - src= 127.0.0.1
 - dst= 127.0.0.1
 - \options\
 - ###[ TCP ]###
 - sport= ftp_data
 - dport= http
 - seq= 0
 - ack= 0
 - dataofs= 5
 - reserved= 0
 - flags= S
 - window= 8192
 - chksum= 0x917c
 - urgptr= 0
 - options= []
 
summary
顯示數(shù)據(jù)包的簡短的摘要
- >>> packet.summary()
 - 'IP / TCP 127.0.0.1:ftp_data > 127.0.0.1:http S'
 
與數(shù)據(jù)包內(nèi)部的字段進(jìn)行交互
- >>> Ether(dst="d8:55:a3:fe:80:78")/IP(dst="8.8.8.8")
 - <Ether dst=d8:55:a3:fe:80:78 type=IPv4 |<IP dst=8.8.8.8 |>>
 - >>> packet=_
 - >>> packet.dst
 - 'd8:55:a3:fe:80:78'
 - >>> packet[IP].dst
 - '8.8.8.8'
 
檢查數(shù)據(jù)包中是否存在層
haslayer方法
- >>> if packet.haslayer(IP):
 - ...: print (packet[IP].dst)
 - ...:
 - 8.8.8.8
 
使用in構(gòu)造
- >>> pkt = IP()/TCP()/DNS()
 - >>> DNS in pkt
 - True
 
Scapy的sprintf
- sprintf()方法是Scapy的強(qiáng)大功能之一,在編寫自定義工具時(shí)非常方便。
 - sprintf 用數(shù)據(jù)包中的值填充格式字符串,就像C語言庫中的sprintf一樣,不同的是這里用數(shù)據(jù)包中的字段值填充格式字符串。
 
- >>> packet.sprintf("Ethernet source is %Ether.src% and IP proto is %IP.proto%")
 - 'Ethernet source is 00:16:3e:0c:d1:ad and IP proto is tcp'
 - >>> a.sprintf("%dst% %IP.dst% vlan=%Dot1Q.vlan%")
 - '00:00:d4:ae:3f:71 192.168.0.1 vlan=42'
 - >>>
 - >>>a.sprintf(" %TCP.flags% | %5s,TCP.flags% | %#05xr,TCP.flags%")
 - ' RA | RA | 0x014'
 
數(shù)據(jù)包處理程序
我們可以使用lambda函數(shù)編寫處理TCP數(shù)據(jù)包的數(shù)據(jù)包處理程序,但該功能僅適用于TCP數(shù)據(jù)包。
- >>> f=lambda x:x.sprintf("%IP.dst%:%TCP.dport%")
 - >>> f(IP(dst="8.8.8.8")/TCP())
 - '8.8.8.8:http'
 - >>> f(IP(dst="8.8.8.8")/UDP())
 - '8.8.8.8:??'
 
還可以使用sprintf()中的條件子字符串來實(shí)現(xiàn)處理其它層的目的。條件子字符串僅在數(shù)據(jù)包中存在某個(gè)層時(shí)才觸發(fā),否則將被忽略。還可以!用于檢查是否缺少圖層。條件子字符串格式: {[!]層:子字符串}
- >>> f=lambda x: x.sprintf("=> {IP:ip=%IP.dst% {UDP:dport=%UDP.dport%}\
 - ...: ... {TCP:%TCP.dport%/%TCP.flags%}{ICMP:type=%r,ICMP.type%}}\
 - ...: ... {!IP:not an IP packet}")
 - >>> f(IP()/TCP())
 - '=> ip=127.0.0.1 http/S'
 - >>> f(IP()/UDP())
 - '=> ip=127.0.0.1 dport=domain'
 - >>> f(IP()/ICMP())
 - '=> ip=127.0.0.1 type=8'
 - >>> f(Ether()/ARP())
 - '=> not an IP packet'
 
導(dǎo)入與導(dǎo)出數(shù)據(jù)
PCAP格式
從PCAP文件導(dǎo)入數(shù)據(jù)包。
- pkts = rdpcap("temp.cap")
 - pkts = sniff(offline="temp.cap")
 
將數(shù)據(jù)包導(dǎo)出到pcap文件。
- wrpcap("temp.cap",pkts)
 
十六進(jìn)制轉(zhuǎn)儲(chǔ)格式
- Scapy允許以各種十六進(jìn)制格式導(dǎo)出數(shù)據(jù)包。
 - 使用hexdump()函數(shù)使用hexdump格式顯示一個(gè)或多個(gè)數(shù)據(jù)包:
 
- >>> hexdump(s)
 - 0000 D8 55 A3 FE 80 78 00 16 3E 0C D1 AD 08 00 45 00 .U...x..>.....E.
 - 0010 00 28 00 01 00 00 40 06 8B 5E AC 11 33 50 08 08 .(....@..^..3P..
 - 0020 08 08 00 14 00 50 00 00 00 00 00 00 00 00 50 02 .....P........P.
 - 0030 20 00 A0 0D 00 00
 
十六進(jìn)制字符串
還可以使用str()函數(shù)將整個(gè)數(shù)據(jù)包轉(zhuǎn)換為十六進(jìn)制字符串
- >>> s
 - <Ether dst=d8:55:a3:fe:80:78 type=IPv4 |<IP frag=0 proto=tcp dst=8.8.8.8 |<TCP dport=http |>>>
 - >>> str(s)
 - WARNING: Calling str(pkt) on Python 3 makes no sense!
 - "b'\\xd8U\\xa3\\xfe\\x80x\\x00\\x16>\\x0c\\xd1\\xad\\x08\\x00E\\x00\\x00(\\x00\\x01\\x00\\x00@\\x06\\x8b^\\xac\\x113P\\x08\\x08\\x08\\x08\\x00\\x14
 - \\x00P\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00P\\x02 \\x00\\xa0\\r\\x00\\x00'"
 
Base64
- Scapy可以使用export_object()函數(shù)導(dǎo)出數(shù)據(jù)包的base64編碼數(shù)據(jù)。
 
- >>> export_object(s)
 - b'eNprYEouTk4sqNTLSaxMLSrWyzHici3JSC3iKmTQDCpk1EiOT85PSU0u5krNAzG4Cpki7BkYGA7PCD20+PC+Qw0VDGJ2PIcnHlrLweDKwKDBwMjA4MB2qDvu0BpB4wAOIGAQYQhggIIAJgWGQwt4GRgKmSPYgPycxJLMPMNClrZC1qBCNnfHGxoeDcsdkv2AoKSQPUkPALURLMU='
 - >>> new_pkt = import_object
 
嗅探
Sniff()
- sniff()函數(shù)可幫助我們捕獲所有流量:
 - 包括count,filter,iface,lfilter,prn,timeout選項(xiàng)。
 
- >>> sniff(count=4, iface='eth0')
 - <Sniffed: TCP:1 UDP:3 ICMP:0 Other:0>
 
可以添加過濾以捕獲需要的數(shù)據(jù)包,使用標(biāo)準(zhǔn)的tcpdump / libpcap語法:
- >>> pkts = sniff(count=1,filter="tcp and host 60.205.177.168 and port 80")
 - >>> pkts.summary()
 - Ether / IP / TCP 172.17.51.80:54578 > 60.205.177.168:http S
 
- 可以做類似tcpdump的簡單流量分析器
 
- >>> pkts = sniff(count=5,filter="host 60.205.177.168",prn=lambda x:x.summary())
 - Ether / IP / TCP 172.17.51.80:54624 > 60.205.177.168:http S
 - Ether / IP / TCP 60.205.177.168:54624 > 172.17.51.80:http S
 - Ether / IP / TCP 172.17.51.80:http > 60.205.177.168:54624 SA
 - Ether / IP / TCP 60.205.177.168:http > 172.17.51.80:54624 SA
 - Ether / IP / TCP 172.17.51.80:54624 > 60.205.177.168:http A
 
- 也可以從pcap文件中嗅探數(shù)據(jù)包。
 
- pkts = sniff(offline='test.pcap')
 - >>> pkts.nsummary()
 - 0000 Ether / IP / TCP 172.16.16.128:1606 > 74.125.95.104:http S
 - 0001 Ether / IP / TCP 74.125.95.104:http > 172.16.16.128:1606 SA
 - 0002 Ether / IP / TCP 172.16.16.128:1606 > 74.125.95.104:http A
 - 0003 Ether / IP / TCP 172.16.16.128:1606 > 74.125.95.104:http PA / Raw
 - 0004 Ether / IP / TCP 74.125.95.104:http > 172.16.16.128:1606 A / Padding
 - >>> sniff(offline='test.pcap', lfilter = lambda s: s[TCP].flags == 18, prn = lambda x: x[IP].dst)
 - 192.168.1.1
 - <Sniffed: TCP:1 UDP:0 ICMP:0 Other:0>
 


















 
 
 




 
 
 
 