Python Scapy 報文構造和解析



Scapy是一款強大的交互式數據包處理工具、數據包生成器、網絡掃描器、網絡發現、攻擊工具和包嗅探工具。能靈活地構造各種數據包、發送數據包、包嗅探、應答和反饋匹配等功能。它可以實現Nmap掃描工具、tcpdump抓包工具 、 tshark工具、Netdiscover網絡掃描工具的功能。

Nmap、Netdiscover、tcpdump和tshark介紹可參考:

本文主要介紹Scapy進行報文構造,報文發送和報文解析。

下載安裝

官網:https://scapy.net/
github地址:https://github.com/secdev/scapy
官方文檔:https://scapy.readthedocs.io/en/latest/

pip install scapy

Scapy 的使用

lsc() 命令:列出scapy通用的操作方法,常用的函數包括:

  • arpcachepoison(用於arp毒化攻擊,也叫arp欺騙攻擊)
  • arping(用於構造一個ARP的who-has包)
  • send:發送3層報文( 如TCP/UDP 協議),不接收數據包
  • sendp:發送2層報文(通過mac地址轉發),不接收
  • sniff:用於網絡嗅探,類似Wireshark和tcpdump抓包
  • sr:發送,接收3層報文,返回有回應的數據包和沒有回應的數據包。
  • sr1:發送,只接收1個響應包
  • srp:發送,接收2層報文
  • srp1:發送,只接收1個響應包
>>> 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
chexdump            : Build a per byte hexadecimal representation
ls                  : List  available layers, or infos on a given layer class or name.
send                : 
sendp               : 
sendpfast           : Send packets at layer 2 using tcpreplay for performance
sniff               : 
split_layers        : Split 2 layers previously bound.
sr                  : 
sr1                 : 
sr1flood            : Flood and receive packets at layer 3 and return only the first answer
srp                 : 
srp1                : 

ls():查看支持的協議

ls(IP):查看IP包的默認參數

報文嗅探

sniff() 函數參數

Scapy使用 sniff() 函數進行報文嗅探, sniff() 方法有以下參數:

def _run(self,
            count=0, store=True, offline=None,
            quiet=False, prn=None, filter=None, lfilter=None,
            L2socket=None, timeout=None, opened_socket=None,
            stop_filter=None, iface=None, started_callback=None,
            session=None, session_args=[], session_kwargs={},
            *arg, **karg):

部分參數定義:

  • count:抓包數量,默認為0,表示無限制
  • store:是否保存抓取的數據包
  • offline:讀取 pcap 文件
  • quiet:設置為True時,會丟棄stderr進程
  • prn:對對每個數據包進行某個操作的函數。例如:prn = lambda x: x.summary();
  • filter:BPF(Berkeley Packet Filter)過濾規則,wireshark過濾也使用的是BPF過濾器。
  • timeout:指定嗅探時間
  • stop_filter:定義一個函數,在抓到指定數據包后停止抓包
  • iface:抓包的接口

filter參數的BPF語法可參考

  1. https://biot.com/capstats/bpf.html
  2. https://www.tcpdump.org/manpages/pcap-filter.7.html

BPF語法示例:

  • dst host 192.168.0.1:目的IP為192.168.0.1的報文
  • host 192.168.0.1:IP地址為192.168.0.1的報文
  • tcp port 80:TCP端口號為80的報文(HTTP報文)
  • tcp and not port 80:除了80端口的TCP報文
  • tcp portrange 1-25:TCP端口范圍1-25的報文
  • not broadcast:排除廣播報文
  • !arp:排除arp報文

sniff() 抓包

from scapy.all import *

package = sniff(iface='WLAN', timeout=10)
wrpcap("test.pcap", package)  # 將抓取的包保存為test.pcap文件

查看保存的報文:

>>> pkts = sniff(offline='test.pcap')
>>> pkts[17].show()
###[ Ethernet ]###
  dst= ff:ff:ff:ff:ff:ff
  src= fc:4d:d4:f8:84:f8
  type= ARP
###[ ARP ]###
     hwtype= 0x1
     ptype= IPv4
     hwlen= 6
     plen= 4
     op= who-has
     hwsrc= fc:4d:d4:f8:84:f8
     psrc= 192.168.101.156
     hwdst= 00:00:00:00:00:00
     pdst= 192.168.101.1
###[ Padding ]###
        load= '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

過濾報文:

>>> sniff(iface='WLAN', timeout=10, filter="tcp port 80", prn=lambda x:x.sprintf("{IP:%IP.src% -> %IP.dst%}"))
183.232.231.174 -> 192.168.0.167
192.168.0.167 -> 183.232.231.174
<Sniffed: TCP:2 UDP:0 ICMP:0 Other:0>

除了使用scapy抓包外,也可以使用tcpdump(Linux)和tshark(Windows)進行抓包。

DHCPv6報文構造

我們首先用Scapy打開一個真實抓到的DHCPv6 Request報文,查看一下報文結構:

from scapy.all import *
pkts = rdpcap('packet.pcap')
pkts[1021].show() # 序號為1022的報文為DHCPv6 Request報文(通過wireshark查看)

報文打印如下:

下面開始構造每一層報文:

from scapy.all import *
ethernet = Ether(dst='00:0c:29:47:f3:2f',src='c8:3a:35:09:ef:a1',type=0x86dd)
ip = IPv6(src ='2001:db8:3333::16',dst='ff02::2')
udp =UDP(sport=546,dport=547)
# dhcpv6 = DHCP6(msgtype = 1)
dhcpv6 = DHCP6_Solicit()
cid = DHCP6OptClientId()
iana = DHCP6OptIA_NA()
iapd_p = DHCP6OptIAPrefix()
iapd = DHCP6OptIA_PD(iapdopt=[iapd_p])
packet = ethernet/ip/udp/dhcpv6/cid/iana/iapd
packet.show()

注意DHCP6 Option - IA Prefix option 的構造方法, IA Prefix選項是包含在IAPD選項內的,所以要賦值給iapdopt,多個option字段用逗號隔開。

其中,字段名稱通過 ls() 命令查看:

ls(DHCP6OptIA_PD)
ls(DHCP6OptIA_NA)

運行上面程序,打印構造的報文:

構造成功!

發送報文

1. 只發不收

  • send:發送3層報文( 如TCP/UDP 協議),不接收數據包
  • sendp:發送2層報文(通過mac地址轉發),不接收
send(packet, iface='eth1', count=2) 
sendp(packet, iface='eth0')

count,發送報文數,默認發送一個報文
iface,指定接口

2. 發且收

  • sr:發送,接收3層報文,返回有回應的數據包和沒有回應的數據包。
  • sr1:發送,只接收1個響應包
  • srp:發送,接收2層報文
  • srloop():循環發送
  • srp1:發送,只接收1個響應包
  • srploop:循環發送
sr(packet, iface='eth1')

報文過濾

在網絡協議的測試中,我們需要檢測某個報文字段是否存在,對抓取到的報文進行解析,可以使用tshark命令解析報文解析(參考文章tcpdump抓包及tshark解包方法介紹)。

當然,Scapy也可以解析數據包,直接上代碼:
查找DHCPv6 Solicit報文,且目的MAC為00:0c:29:d9:98:c7

from scapy.all import *
import re

package = "package.pcap"
field = 'dst=00:0c:29:d9:98:c7'
pkts = rdpcap(package)
for packet in pkts:    
    if packet.haslayer('DHCP6_Solicit'):        
        packet_text = repr(packet)        
        if re.search(field, packet_text, re.IGNORECASE):
            print("666")

其中,repr內置函數用於返回對象的 string 格式。除了rdpcap()方法讀取報文文件外,也可以使用嗅探函數sniff():

pkts = sniff(offline='packet_solicit.pcap')

如果不知道目標字段寫法,可以先打印一下:

>>> from scapy.all import *
>>> pkts = rdpcap('packet_solicit.pcap')
>>> pkts[3]
<Ether  dst=ff:ff:ff:ff:ff:ff src=00:0c:29:d9:98:c7 type=IPv6 |<IPv6  version=6 tc=0 fl=0 plen=46 nh=UDP hlim=64 src=fe80::20c:29ff:fed9:98c7 dst=ff02::1:2 |<UDP  sport=dhcpv6_client dport=dhcpv6_server len=46 chksum=0x764d |<DHCP6_Solicit  msgtype=SOLICIT trid=0x0 |<DHCP6OptClientId  optcode=CLIENTID optlen=14 duid=<DUID_LLT  type=Link-layer address plus time hwtype=Ethernet (10Mb) timeval=Sat, 01 Jan 2000 00:00:00 +0000 (946684800) lladdr=00:0c:29:d9:98:c7 |> |<DHCP6OptIA_NA  optcode=IA_NA optlen=12 iaid=0x0 T1=0 T2=0 |>>>>>>
--THE END--

歡迎關注公眾號:「測試開發小記」及時接收最新技術文章!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM