Scapy入门

Scapy是一个Python程序,它允许用户发送、嗅探、分析和伪造网络包。这种能力允许构建能够探测、扫描或攻击网络的工具。

中文文档:https://www.osgeo.cn/scapy/index.html

下载Scapy

1
pip install scapy
  • Windows需要安装Npcap
  • Linux确保安装了tcpdump
    1
    yum install tcpdump
    在终端输入scapy即可进入交互式命令行
    20230512111911

Scapy基本操作

在Scapy中,ls()函数用于列出当前可用的网络协议和数据包。
如果想查看可用的TCP选项,可以使用ls(TCP)

构建一个IP数据包

  1. 首先查看IP()类中的属性。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    >>> ls(IP)
    version : BitField (4 bits) = ('4')
    ihl : BitField (4 bits) = ('None')
    tos : XByteField = ('0')
    len : ShortField = ('None')
    id : ShortField = ('1')
    flags : FlagsField = ('<Flag 0 ()>')
    frag : BitField (13 bits) = ('0')
    ttl : ByteField = ('64')
    proto : ByteEnumField = ('0')
    chksum : XShortField = ('None')
    src : SourceIPField = ('None')
    dst : DestIPField = ('None')
    options : PacketListField = ('[]')
  2. 设置属性值
    1
    2
    >>> IP(dst="192.168.0.1",ttl=64)
    <IP ttl=64 dst=192.168.0.1 |>

    采用分层的形式来构造数据包

    一个数据包是由多层协议组合而成,Scapy中实现多层协议通过符号/分层实现,协议之间使用/分开,按照从下往上从左往右排列,如Ether()/IP()/TCP()
    构造一个完整的HTTP数据包:
    1
    2
    3
    4
    5
    >>> a=Ether()/IP(dst="www.slashdot.org")/TCP()/"GET /index.html HTTP/1.0 \n\n"
    >>> b=raw(a)
    >>> c=Ether(b)
    >>> c
    <Ether dst=cc:08:fb:ea:3f:71 src=f8:e4:3b:e3:98:17 type=IPv4 |<IP version=4 ihl=5 tos=0x0 len=67 id=1 flags= frag=0 ttl=64 proto=tcp chksum=0x34e6 src=192.168.0.190 dst=104.18.28.86 |<TCP sport=ftp_data dport=http seq=0 ack=0 dataofs=5 reserved=0 flags=S window=8192 chksum=0x77e3 urgptr=0 |<Raw load='GET /index.html HTTP/1.0 \n\n' |>>>>

    变量a是一个Scapy数据包对象,其中包含了以太网帧、IP数据包、TCP分段和HTTP请求的数据。
    b = raw(a)从Scapy数据包对象a中提取原始字节流。raw()方法将数据包序列化为可以在网络上传输的字节流格式。
    c = Ether(b)的目的是在序列化后的数据包b周围创建一个以太网帧。这是必要的,因为当我们在局域网上发送数据包时,它们需要封装在一个以太网 帧中,其中包括源设备和目标设备的硬件地址(MAC地址)。
    因此,通过将序列化后的数据包b嵌入到以太网帧中创建了c。然后可以将结果数据包发送到网络上。

python代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from scapy.all import *

# 构造 Ehter 头部
ether = Ether()
# 构造 IP 头部
ip = IP(dst="www.slashdot.org")
# 构造 TCP 头部
tcp = TCP()
# 构造 HTTP 头部和负载数据
http_header = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
http_payload = ""
# 构造完整数据包
pkt = raw(ether / ip / tcp / http_header / http_payload)
packet = Ether(pkt)
# 查看完整数据包
print(repr(packet))

pcap文件读取和保存

1
2
rdpcap("test.cap")
wrpcap("test.cap")

发送和接受数据包

send()函数工作在第三层,sendp()函数工作在第二层,这两个函数都可以发送数据包,但是sendp()函数可以发送以太网帧,而send()函数只能发送IP数据包。
sr()函数用于发送一个或多个数据包,并等待响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from scapy.all import *

# 设置路由表项
conf.route.add(net="0.0.0.0/0", gw="192.168.0.1")

# 创建一个 ICMP 数据包
packet = IP(dst="114.114.114.114")/ICMP()

# 发送数据包并等待回复
response = sr(packet, timeout=1)

# 显示响应信息
for i in response:
print(i.show())

sr1()函数用于发送一个数据包,并等待响应,但是只返回第一个响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from scapy.all import *


# 设置路由表项
conf.route.add(net="0.0.0.0/0", gw="192.168.0.1")

# 创建一个 ICMP 数据包
packet = IP(dst="8.8.8.8")/ICMP()

# 发送数据包并等待回复
response = sr1(packet, timeout=1)

# 显示响应信息
if response:
print(response.show())

srloop()函数在第三层连续发送数据包,有接收功能,且连续接收数据包。
srp()函数用于发送一个或多个以太网帧,并等待响应。

之前区别:
srp()函数发送数据包并返回收到的响应数据包列表,而srp1()函数只返回一个响应数据包。
srp()函数可以发送多个数据包,并通过过滤器指定要接收的响应数据包,而srp1()函数只能发送一个数据包并返回第一个响应数据包。
不同的是在sr()函数中,如果没有收到响应数据包,则会返回一个空列表,而在sr1()函数中,如果没有收到响应数据包,则会返回None。

1
srp1(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(op=1,pdst="192.168.0.1"),timeout=1,iface="iface_name")

在发送二层和三层数据包时,可以使用iface参数指定要使用的网络接口。如果没有指定网络接口,则会使用默认网络接口。默认网卡为电脑自带的网卡,而我使用的扩展网卡,通过srp()函数发送数据包时,需要指定网卡名称,通过sr()函数发送数据包时,需要配置路由表项。默认的网卡不需要配置这些。

“发送和接收”功能族 是 Scapy 的核心。他们返回了两个列表。第一个元素是成对的列表(发送数据包、应答),第二个元素是未应答数据包的列表。

1
2
3
4
5
6
7
8
9
10
11
12
>>> sr(IP(dst="192.168.0.1")/TCP(dport=[21,22,23]))
Begin emission:
Finished sending 3 packets.
.*.*.*
Received 6 packets, got 3 answers, remaining 0 packets
(<Results: TCP:3 UDP:0 ICMP:0 Other:0>,
<Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)
>>> ans,unans = _
>>> ans.summary()
IP / TCP 192.168.0.190:ftp_data > 192.168.0.1:ftp S ==> IP / TCP 192.168.0.1:ftp > 192.168.0.190:ftp_data RA / Padding
IP / TCP 192.168.0.190:ftp_data > 192.168.0.1:ssh S ==> IP / TCP 192.168.0.1:ssh > 192.168.0.190:ftp_data RA / Padding
IP / TCP 192.168.0.190:ftp_data > 192.168.0.1:telnet S ==> IP / TCP 192.168.0.1:telnet > 192.168.0.190:ftp_data RA / Padding

三次握手​​​​​​​原理:
第1次握手:客户端发送一个带有SYN(synchronize)标志的数据包给服务端;
第2次握手:服务端接收成功后,回传一个带有SYN/ACK标志的数据包传递确认信息,表示我收到了;
第3次握手:客户端再回传一个带有ACK标志的数据包,表示我知道了,握手结束。
SYN:开始会话
ACK: 应答
RST: 中断连接
FIN: 结束会话

ARP扫描

ARP请求返回的QueryAnswer对象如下:

1
2
3
4
QueryAnswer(query=<Ether  dst=ff:ff:ff:ff:ff:ff type=ARP |
<ARP pdst=192.168.0.1 |>>, answer=<Ether dst=a1:b1:c1:d1:e1:f1 src=a2:b2:c2:d2:e2:f2 type=ARP |
<ARP hwtype=Ethernet (10Mb) ptype=IPv4 hwlen=6 plen=4 op=is-at hwsrc=a2:b2:c2:d2:e2:f2 psrc=192.168.0.1 hwdst=a1:b1:c1:d1:e1:f1 pdst=192.168.0.190 |
<Padding load='\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 ' |

字段解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
查询请求(Query):发送了一个以太网(Ether)帧,其目的地址(dst)为广播地址(ff:ff:ff:ff:ff:ff),帧类型(type)为ARP。ARP协议请求被用于查找目标IP地址所对应的MAC地址。

目的地址(dst): 广播地址,表示该请求需要被所有本地网络上的主机收到,并被目标主机所应答。
类型(type): ARP协议类型。
查询响应(answer):接收到一个以太网(Ether)帧,其源地址(src)为a2:b2:c2:d2:e2:f2,目的地址(dst)为a1:b1:c1:d1:e1:f1,帧类型(type)为ARP。该响应是对前面的查询请求的回应,包含了目标IP地址对应的MAC地址。

源地址(src): 发送ARP响应的主机的MAC地址。
目的地址(dst): 接收ARP响应的主机的MAC地址。
硬件类型(hwtype): 表示使用的网络硬件类型,Ethernet (10Mb)表示以太网。
协议类型(ptype): 表示使用的协议类型,IPv4表示互联网协议版本4。
硬件地址长度(hwlen): 表示硬件地址的长度,以字节为单位。
协议地址长度(plen): 表示协议地址的长度,以字节为单位。
操作类型(op): 表示ARP请求或响应的类型,is-at表示该响应包含了目标MAC地址。
源硬件地址(hwsrc): 发送ARP响应的主机的MAC地址。
源协议地址(psrc): 发送ARP响应的主机的IP地址。
目的硬件地址(hwdst): 接收ARP响应的主机的MAC地址。
目的协议地址(pdst): 接收ARP响应的主机的IP地址。

python实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from scapy.all import *


packet = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.168.0.0/24")

# iface参数指定要使用的网络接口
ifaces = "iface_name"
ans, unans = srp(packet, iface=ifaces, timeout=2)

result = []

for query, answer in ans:
result.append([answer[ARP].psrc,answer[ARP].hwsrc])

for ip, mac in result:
print(ip, mac)

SYN扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
>>> sr(IP(dst="192.168.0.1")/TCP(sport=RandShort(),dport=(78,100),flags="S"),timeout=1)
Begin emission:
Finished sending 23 packets.
....*.*.*...**.*..**.*.*.*.*.*.*..*.*.*.*.*.*.*.*..*
Received 52 packets, got 23 answers, remaining 0 packets
(<Results: TCP:23 UDP:0 ICMP:0 Other:0>,
<Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)
>>> ans,unans = _
>>> ans.summary(lambda s,r: r.sprintf("%TCP.sport% \t %TCP.flags%"))
78 RA
finger RA
http SA
hosts2_ns RA
82 RA
83 RA
84 RA
85 RA
86 RA
87 RA
kerberos RA
89 RA
90 RA
91 RA
92 RA
93 RA
94 RA
95 RA
96 RA
97 RA
98 RA
99 RA
100 RA

python实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# SYN scanning
def syn_scan(ip, port):
p = IP(dst=ip) / TCP(sport=RandShort(), dport=int(port), flags="S")
response = sr1(p, timeout=1)
if response == None:
pass
else:
if response[TCP].flags == 'SA':
print(ip, "port", port, "is open.")
elif response[TCP].flags == 'RA':
print(ip, "port", port, "is closed.")
else:
print(ip, "port", port, "is filtered.")


if __name__ == "__main__":
syn_scan("192.168.0.1", "80")

Scapy嗅探

使用Scapy进行嗅探网络数据包,可以捕获和分析传输过程中的所有数据包。

1
2
3
4
5
6
7
8
 from scapy.all import *


def packet_handler(pkt):
print(pkt.show())


sniff(prn=packet_handler, count=10)

定义了一个名为packet_handler()的回调函数,用于处理每个捕获到的数据包。使用sniff()函数捕获前10个数据包,并将每个数据包传递给packet_handler()函数进行处理。在packet_handler()函数中,使用show()方法打印出每个数据包的详细信息。

sniff参数详解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
count -- 要捕获的数据包数。0表示无穷大。
store -- 是存储嗅探包还是丢弃它们
prn -- 应用于每个数据包的函数。如果返回某个内容,则显示该内容。--例如:prn=lambda x:x.summary()
session -- 会话=用于处理数据包流的流解码器。--例如:session=TCPSession请参阅下面的详细信息。
filter -- 要应用的BPF筛选器。
lfilter -- 应用于每个包的Python函数,以确定是否可以执行进一步的操作。--例如:lfilter=lambda x:x.haslayer(填充)
offline -- PCAP文件(或PCAP文件列表)从中读取数据包,而不是嗅探它们
quiet -- 当设置为True时,将丢弃进程stderr(默认值:False)。
timeout -- 在给定时间后停止嗅探(默认值:无)。
L2socket -- 使用提供的L2socket(默认值:use conf.L2listen)。
opened_socket -- 提供一个对象(或一个对象列表),以便在上使用.recv()。
stop_filter -- Python函数应用于每个包,以确定是否必须在此包之后停止捕获。--例如:stop_filter=lambda x:x.haslayer(TCP)
iface -- 接口或接口列表(默认值:无用于在所有接口上探查)。
monitor -- 使用监视器模式。可能并非所有操作系统都可用
started_callback -- 在嗅探器开始嗅探时立即调用(默认值:None)。

Scapy 中常用的一些协议层:

1
2
3
4
5
6
7
8
9
Ether 协议层:以太网协议层,用于操作以太网数据帧。
ARP 协议层:地址解析协议,用于解析 IP 地址对应的 MAC 地址。
IP 协议层:互联网协议,用于在 Internet 中传输数据包。
TCP 协议层:传输控制协议,提供可靠的、有序的、基于连接的数据通信服务。
UDP 协议层:用户数据报协议,提供无连接、不可靠的数据通信服务。
ICMP 协议层:Internet 控制消息协议,用于传递错误信息和诊断信息。
DNS 协议层:域名系统协议,用于将域名转换为 IP 地址。
HTTP 协议层:超文本传输协议,用于在 Web 浏览器和 Web 服务器之间传输数据。
TLS 协议层:安全传输层协议,用于在网络上提供加密和身份验证服务。

获取Raw层的内容:

1
2
3
4
5
6
7
8
9
10
from scapy.all import *


def callBack(packet):
if packet.haslayer('Raw'):
http = packet.payload.payload.payload
print(http[Raw].load.decode("utf-8", errors="ignore"))


sniff(prn=callBack, count=0)

haslayer():判断数据包是否包含指定协议层。
getlayer():获取指定协议层的数据包对象。

使用payload就相当于向上解封装一次。

也可以直接通过packet[IP].src直接获取想要的值。