交互式数据包处理程序 Scapy 入门指南

Posted Q-WHai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了交互式数据包处理程序 Scapy 入门指南相关的知识,希望对你有一定的参考价值。

概述

Scapy 是一个强大的交互式数据包处理程序(使用python编写)。它能够伪造或者解码大量的网络协议数据包,能够发送、捕捉、匹配请求和回复包等等。它可以很容易地处理一些典型操作,比如端口扫描,tracerouting,探测,单元测试,攻击或网络发现(可替代hping,NMAP,arpspoof,ARP-SK,arping,tcpdump,tethereal,P0F等)。最重要的他还有很多更优秀的特性——发送无效数据帧、注入修改的802.11数据帧、在WEP上解码加密通道(VOIP)、ARP缓存攻击(VLAN)等,这也是其他工具无法处理完成的。下面就先通过一些实例来学习一下 Scapy 这个强大的工具吧。


版权说明

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
本文作者:Coding-Naga
发表日期: 2016年4月13日
本文链接:http://blog.csdn.net/lemon_tree12138/article/details/51141440
来源:CSDN
更多内容:分类 >> 黑客的隐形衣


实验环境

  • Centos 6.5
  • Python 2.6.6
  • setuptools-20.7.0
  • prettytable-0.7.2
  • Scapy (2.3.2)

功能列表

基本测试

登入 Scapy 环境

$ scapy

框架包的引入

>>> from scapy.all import *

这里要注意的是不要使用右边的这种方式导入包:*from scapy import **

查看配置信息

conf 变量保存了配置信息

>>> conf
ASN1_default_codec = <ASN1Codec BER[1]>
AS_resolver = <scapy.as_resolvers.AS_resolver_multi instance at 0x13c1dd0>
( 此处省略 N 行 )
verb       = 2
version    = '2.3.2'
warning_threshold = 5
wepkey     = ''

查看 scapy 支持的指令集

>>> 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
( 此处省略 N 行 )
wireshark           : Run wireshark on a list of packets
wrpcap              : Write a list of packets to a pcap file

比如这里的 arping ,我们就可以这样来使用:
arping
得到所在局域网内所有可用的ip与mac的对应关系

>>> arping("172.16.2.79/80")
Begin emission:
Finished to send 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
  30:5a:3a:45:1a:28 172.16.2.79
(<ARPing: TCP:0 UDP:0 ICMP:0 Other:1>, <Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)

查看 scapy 中已实现的网络协议

缺省参数模式

>>> ls()
AH         : AH
ARP        : ARP
ASN1_Packet : None
ATT_Error_Response : Error Response
ATT_Exchange_MTU_Request : Exchange MTU Request
( 此处省略 N 行 )
_IPv6ExtHdr : Abstract IPV6 Option Header
_MobilityHeader : Dummy IPv6 Mobility Header

携带参数模式

>>> ls(UDP)
sport      : ShortEnumField            = (53)
dport      : ShortEnumField            = (53)
len        : ShortField                = (None)
chksum     : XShortField               = (None)

ls() 中携带的参数可以是任何的一个具体的包,常用的有ARP、Ether、ICMP、IP、UDP、TCP,也支持SNMP、DHCP、STP等。

IP 模块的使用

我们可以像在 python 中一样实例化一个 IP 对象。

>>> data = IP()
>>> data
<IP  |>

也可以传入需要自定义的参数

>>> data = IP(dst="172.16.2.79")
>>> data
<IP  dst=172.16.2.79 |>

查看 IP 模块对象的所有信息

>>> data = IP()
>>> data.show()
###[ IP ]###
  version= 4
  ihl= None
  tos= 0x0
  len= None
  id= 1
  flags=
  frag= 0
  ttl= 64
  proto= ip
  chksum= None
  src= 127.0.0.1
  dst= 127.0.0.1
  \\options\\
>>> data = IP(dst="172.16.2.79")
>>> data.show()
###[ IP ]###
  version= 4
  ihl= None
  tos= 0x0
  len= None
  id= 1
  flags=
  frag= 0
  ttl= 64
  proto= ip
  chksum= None
  src= 172.16.2.91
  dst= 172.16.2.79
  \\options\\

发送报文

可以将上面的 IP 对象封装成一个数据包发送出去。

>>> send(data, iface="eth0")
.
Sent 1 packets.

接收报文

这边我所做的测试是,在本地开启两个 Putty 客户端,远程连接 172.16.2.91 虚拟机。 putty-1 用于 scapy 监听接收数据包, putty-2 用于向 172.16.2.91 发送指令。这里我发送的测试指令是 ls.
测试的结果如下:

>>> receive = sniff(filter="tcp and host 172.16.2.79", count=2)
>>> receive
<Sniffed: TCP:2 UDP:0 ICMP:0 Other:0>
>>> receive.show()
0000 Ether / IP / TCP 172.16.2.79:58081 > 172.16.2.91:ssh A / Padding
0001 Ether / IP / TCP 172.16.2.79:61186 > 172.16.2.91:ssh PA / Raw
>>> receive[0]
<Ether  dst=08:00:27:24:b8:a3 src=30:5a:3a:45:1a:28 type=0x800 |<IP  version=4L ihl=5L tos=0x0 len=40 id=20835 flags=DF frag=0L ttl=64 proto=tcp chksum=0x8ca2 src=172.16.2.79 dst=172.16.2.91 options=[] |<TCP  sport=58081 dport=ssh seq=2977133173 ack=3389913828 dataofs=5L reserved=0L flags=A window=16249 chksum=0x47bd urgptr=0 |<Padding  load='\\x00\\x00\\x00\\x00\\x00\\x00' |>>>>
>>> receive[1]
<Ether  dst=08:00:27:24:b8:a3 src=30:5a:3a:45:1a:28 type=0x800 |<IP  version=4L ihl=5L tos=0x0 len=104 id=20838 flags=DF frag=0L ttl=64 proto=tcp chksum=0x8c5f src=172.16.2.79 dst=172.16.2.91 options=[] |<TCP  sport=61186 dport=ssh seq=3311241534 ack=3819002209 dataofs=5L reserved=0L flags=PA window=16277 chksum=0x4540 urgptr=0 options=[] |<Raw  load='\\xc0\\xday\\xa2X\\xe5\\'=QS\\xf9\\x1e\\xe2|\\xa0\\xb4\\xb5Y\\r\\xb0e\\x86\\x02\\x13x\\x19E[\\x94\\x0c\\xff\\xec#\\x1c?;W\\xab\\x18\\xf6"\\x90\\'\\xd9\\x94\\x01G\\xb0\\xc6\\x07\\x08\\xc3\\'\\r\\x7f\\xa9jo\\xa1\\x04\\xc1\\\\\\x13y' |>>>>

更多详细过程,请参见下面的数据嗅探及过滤章节。


细说数据发送

在上面的基本测试阶段,对发送数据进行一些常规测试。本节会是对发送数据的一个全面解析。

send

在第三层发送数据包,但没有接收功能。

>>> send(IP(dst="www.baidu.com",ttl=1)/ICMP())
.
Sent 1 packets.
>>> send(IP(dst="192.168.115.188")/ICMP())
.
Sent 1 packets.
>>> send(IP(dst="www.baidu.com")/UDP()/NTP(version=4), loop=2)
>>> send(IP(dst="www.baidu.com")/fuzz(UDP()/NTP(version=4)), loop=2)

fuzz函数的作用:可以更改一些默认的不可以被计算的值(比如校验和checksums),更改的值是随机的,但是类型是符合字段的值的。

向某一个 IP 发送一个数据包
data_sender.py

import struct
from scapy.all import *

# data = struct.pack('=BHI', 0x12, 20, 1000)
data = struct.pack('=BHI', 0x12, 0x4EF3, 0x76)
pkt = IP(src='172.16.2.91', dst='172.16.2.79')/UDP(sport=12345,dport=5555)/data
send(pkt, inter=1, count=5)

上面的 python 代码的功能是从 172.16.2.91/12345 每隔 1 秒就向 172.16.2.79/5555 发送 5 个相同的数据包,这个数据包中包含了三个数据,分别为:0x12, 0x4EF3, 0x76。如果在 172.16.2.79 上使用抓包工具,也是可以捕获这个数据包的。

sendp

在第二层发送数据包,同样没有接收功能。

>>> sendp(Ether()/IP(dst="www.baidu.com",ttl=1)/ICMP())
.
Sent 1 packets.
>>> sendp(Ether()/IP(dst="192.168.115.188",ttl=(1,4)),iface="eth0")
....
Sent 4 packets.
>>> sendp("hello ,i am walfred ",iface="eth0",loop=1,inter=0.2)

sr

在第三层发送数据包,有接收功能。

>>> sr(IP(dst="www.baidu.com")/TCP(dport=[21, 22, 23]))
Begin emission:
.Finished to send 3 packets.
.......^C
Received 8 packets, got 0 answers, remaining 3 packets
(<Results: TCP:0 UDP:0 ICMP:0 Other:0>, <Unanswered: TCP:3 UDP:0 ICMP:0 Other:0>)
>>> p = sr(IP(dst="www.baidu.com",ttl=1)/ICMP())
Begin emission:
....Finished to send 1 packets.
...*
Received 8 packets, got 1 answers, remaining 0 packets
>>> p
(<Results: TCP:0 UDP:0 ICMP:1 Other:0>, <Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)
>>> p[0]
<Results: TCP:0 UDP:0 ICMP:1 Other:0>
>>> p[0].show()
0000 IP / ICMP 172.16.2.91 > 180.97.33.108 echo-request 0 ==> IP / ICMP 172.16.2.20 > 172.16.2.91 time-exceeded ttl-zero-during-transit / IPerror / ICMPerror

连续发送ttl=1,2,3,4四个包的情况

>>> p=sr(IP(dst="www.baidu.com",ttl=(1,4))/ICMP())
Begin emission:
....*.*Finished to send 4 packets.
.*.*
Received 11 packets, got 4 answers, remaining 0 packets
>>> p
(<Results: TCP:0 UDP:0 ICMP:4 Other:0>, <Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)
>>> p[0].show()
0000 IP / ICMP 172.16.2.91 > 180.97.33.107 echo-request 0 ==> IP / ICMP 172.16.2.20 > 172.16.2.91 time-exceeded ttl-zero-during-transit / IPerror / ICMPerror
0001 IP / ICMP 172.16.2.91 > 180.97.33.107 echo-request 0 ==> IP / ICMP 121.225.2.1 > 172.16.2.91 time-exceeded ttl-zero-during-transit / IPerror / ICMPerror
0002 IP / ICMP 172.16.2.91 > 180.97.33.107 echo-request 0 ==> IP / ICMP 218.2.151.29 > 172.16.2.91 time-exceeded ttl-zero-during-transit / IPerror / ICMPerror / Padding
0003 IP / ICMP 172.16.2.91 > 180.97.33.107 echo-request 0 ==> IP / ICMP 202.102.69.70 > 172.16.2.91 time-exceeded ttl-zero-during-transit / IPerror / ICMPerror / Padding

sr1

在第三层发送数据包,有接收功能,但只接收第一个包。以上面的发送四个包为例

>>> q=sr1(IP(dst="www.baidu.com",ttl=(1,4))/ICMP())
Begin emission:
.....*.**Finished to send 4 packets.
..*
Received 12 packets, got 4 answers, remaining 0 packets
>>> q
<IP  version=4L ihl=5L tos=0xc0 len=56 id=30311 flags= frag=0L ttl=128 proto=icmp chksum=0x670e src=172.16.2.20 dst=172.16.2.91 options=[] |<ICMP  type=time-exceeded code=ttl-zero-during-transit chksum=0xf4ff reserved=0 length=0 unused=None |<IPerror  version=4L ihl=5L tos=0x0 len=28 id=1 flags= frag=0L ttl=1 proto=icmp chksum=0x35a8 src=172.16.2.91 dst=180.97.33.108 options=[] |<ICMPerror  type=echo-request code=0 chksum=0xf7ff id=0x0 seq=0x0 |>>>>
>>> q.show();
###[ IP ]###
  version= 4L
  ihl= 5L
  tos= 0xc0
  len= 56
  id= 30311
  flags=
  frag= 0L
  ttl= 128
  proto= icmp
  chksum= 0x670e
  src= 172.16.2.20
  dst= 172.16.2.91
  \\options\\
###[ ICMP ]###
     type= time-exceeded
     code= ttl-zero-during-transit
     chksum= 0xf4ff
     reserved= 0
     length= 0
     unused= None
###[ IP in ICMP ]###
        version= 4L
        ihl= 5L
        tos= 0x0
        len= 28
        id= 1
        flags=
        frag= 0L
        ttl= 1
        proto= icmp
        chksum= 0x35a8
        src= 172.16.2.91
        dst= 180.97.33.108
        \\options\\
###[ ICMP in ICMP ]###
           type= echo-request
           code= 0
           chksum= 0xf7ff
           id= 0x0
           seq= 0x0

srloop

在第三层工作

>>> p=srloop(IP(dst="www.baidu.com",ttl=1)/ICMP())
RECV 1: IP / ICMP 172.16.2.20 > 172.16.2.91 time-exceeded ttl-zero-during-transit / IPerror / ICMPerror
RECV 1: IP / ICMP 172.16.2.20 > 172.16.2.91 time-exceeded ttl-zero-during-transit / IPerror / ICMPerror
RECV 1: IP / ICMP 172.16.2.20 > 172.16.2.91 time-exceeded ttl-zero-during-transit / IPerror / ICMPerror
^C
Sent 3 packets, received 3 packets. 100.0% hits.
>>> p=srloop(IP(dst="www.baidu.com",ttl=1)/ICMP(),inter=3,count=2)
RECV 1: IP / ICMP 172.16.2.20 > 172.16.2.91 time-exceeded ttl-zero-during-transit / IPerror / ICMPerror
RECV 1: IP / ICMP 172.16.2.20 > 172.16.2.91 time-exceeded ttl-zero-during-transit / IPerror / ICMPerror

Sent 2 packets, received 2 packets. 100.0% hits.

这里第一条语句在执行时,将会不停的ping百度,第二条执行时每隔3秒ping一次,一共执行两次。inter表示间隔,count记录次数。

srp

在第二层发送数据包,有接收功能。

>>> srp(IP(dst="192.168.115.1")/TCP(dport=[21,22,23]))
>>> p=srp(IP(dst="www.baidu.com",ttl=1)/ICMP())

srp1

在第二层发送数据包,有接收功能,但只接收第一个包。以上面的发送四个包为例

>>> q=srp1(IP(dst="www.baidu.com",ttl=(1,4))/ICMP())

srploop

在第三层工作

>>> p = srploop(IP(dst="172.16.2.79", ttl=4)/TCP())
fail 1: IP / TCP 172.16.2.91:ftp_data > 172.16.2.79:http S
fail 1: IP / TCP 172.16.2.91:ftp_data > 172.16.2.79:http S
fail 1: IP / TCP 172.16.2.91:ftp_data > 172.16.2.79:http S
send...
Sent 3 packets, received 0 packets. 0.0% hits.
>>> p = srploop(IP(dst="www.baidu.com", ttl=4)/ICMP())
fail 1: IP / ICMP 172.16.2.91 > 180.97.33.107 echo-request 0
fail 1: IP / ICMP 172.16.2.91 > 180.97.33.107 echo-request 0
send...
Sent 2 packets, received 0 packets. 0.0% hits.

端口扫描

TCP 连接扫描

客户端与服务器建立 TCP 连接要进行一次三次握手,如果进行了一次成功的三次握手,则说明端口开放。

# encoding=utf-8

import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import *

dst_ip = "180.97.33.107"  # 百度 IP
src_port = RandShort()
dst_port = 80

tcp_connect_scan_resp = sr1(IP(dst=dst_ip)/TCP(sport=src_port, dport=dst_port, flags="S"), timeout=10)
if str(type(tcp_connect_scan_resp)) == "":
    print("Closed")
elif tcp_connect_scan_resp.haslayer(TCP):
    if tcp_connect_scan_resp.getlayer(TCP).flags == 0x12:
        send_rst = sr(IP(dst=dst_ip)/TCP(sport=src_port, dport=dst_port, flags="AR"), timeout=10)
        print("Open")
elif tcp_connect_scan_resp.getlayer(TCP).flags == 0x14:
    print("Closed")
Begin emission:
...Finished to send 1 packets.
.*
Received 5 packets, got 1 answers, remaining 0 packets
Begin emission:
.Finished to send 1 packets.
.................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
Received 498 packets, got 0 answers, remaining 1 packets
Open

TCP SYN 扫描(也称为半开放扫描或stealth扫描)

同 TCP 连接扫描非常相似。同样是客户端向服务器发送一个带有 SYN 标识和端口号的数据包,如果目标端口开发,则会返回带有 SYN 和 ACK 标识的 TCP 数据包。但是,这时客户端不会返回 RST+ACK 而是返回一个只带有 RST 标识的数据包。这种技术主要用于躲避防火墙的检测。

# encoding=utf-8

import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import *

dst_ip = "180.97.33.107"  # 百度 IP
src_port = RandShort()
dst_port = 80

stealth_scan_resp = sr1(IP(dst=dst_ip)/TCP(sport=src_port, dport=dst_port, flags="S"), timeout=10)
if str(type(stealth_scan_resp)) == "":
    print("Filtered")
elif stealth_scan_resp.haslayer(TCP):
    if stealth_scan_resp.getlayer(TCP).flags == 0x12:
        send_rst = sr(IP(dst=dst_ip)/TCP(sport=src_port, dport=dst_port, flags="R"), timeout=10)
        print("Open")
    elif stealth_scan_resp.getlayer(TCP).flags == 0x14:
        print("Closed")
elif stealth_scan_resp.haslayer(ICMP):
    if int(stealth_scan_resp.getlayer(ICMP).type) == 3 and int(stealth_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]:
        print("Filtered")
Begin emission:
...Finished to send 1 packets.
..*
Received 6 packets, got 1 answers, remaining 0 packets
Begin emission:
Finished to send 1 packets.
..........................................................................................................................................................................................................................................
Received 234 packets, got 0 answers, remaining 1 packets
Open

TCP 圣诞树(Xmas Tree)扫描

在圣诞树扫描中,客户端会向服务器发送带有 PSH,FIN,URG 标识和端口号的数据包给服务器。如果目标端口是开放的,那么不会有任何来自服务器的回应。如果服务器返回了一个带有 RST 标识的 TCP 数据包,那么说明端口处于关闭状态。但如果服务器返回了一个 ICMP 数据包,其中包含 ICMP 目标不可达错误类型3以及 ICMP 状态码为1,2,3,9,10或13,则说明目标端口被过滤了无法确定是否处于开放状态。

# encoding=utf-8

import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import *

dst_ip = "180.97.33.107"  # 百度 IP
src_port = RandShort()
dst_port = 80

xmas_scan_resp = sr1(IP(dst=dst_ip)/TCP(dport=dst_port, flags="FPU"), timeout=10)
if str(type(xmas_scan_resp)) == "":
    print("Open|Filtered")
elif xmas_scan_resp.haslayer(TCP):
    if xmas_scan_resp.getlayer(TCP).flags == 0x14:
        print("Closed")
elif xmas_scan_resp.haslayer(ICMP):
    if int(xmas_scan_resp.getlayer(ICMP).type) == 3 and int(xmas_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]:
        print("Filtered")
Begin emission:
...Finished to send 1 packets.
.....................................................................................................................................................................................................................................................................................................................................................................................................................................................................
Received 456 packets, got 0 answers, remaining 1 packets
Traceback (most recent call last):
  File "scan.py", line 12, in <module>
    elif(xmas_scan_resp.haslayer(TCP)):
AttributeError: 'NoneType' object has no attribute 'haslayer'

这里扫描百度(外网)出现异常状况,于是将百度的 IP 地址替换成了本地的主机 IP 地址。再次执行结果如下:

Begin emission:
...Finished to send 1 packets.
.*
Received 5 packets, got 1 answers, remaining 0 packets
Closed

TCP FIN 扫描

FIN 扫描会向服务器发送带有 FIN 标识和端口号的 TCP 数据包。如果没有服务器端回应则说明端口开放。如果服务器返回一个 RST 数据包,则说明目标端口是关闭的。如果服务器返回了一个 ICMP 数据包,其中包含 ICMP 目标不可达错误类型3以及 ICMP 代码为1,2,3,9,10或13,则说明目标端口被过滤了无法确定端口状态。

# encoding=utf-8

import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import *

dst_ip = "180.97.33.107"  # 百度 IP
src_port = RandShort()
dst_port = 80

fin_scan_resp = sr1(IP(dst=dst_ip)/TCP(dport=dst_port, flags="F"), timeout=10)
if str(type(fin_scan_resp)) == "":
    print("Open|Filtered")
elif fin_scan_resp.haslayer(TCP):
    if fin_scan_resp.getlayer(TCP).flags == 0x14:
        print("Closed")
elif fin_scan_resp.haslayer(ICMP):
    if int(fin_scan_resp.getlayer(ICMP).type) == 3 and int(fin_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]:
        print("Filtered")
Begin emission:
...Finished to send 1 packets.
..........................................................................................................................................................................................................
Received 205 packets, got 0 answers, remaining 1 packets
Traceback (most recent call last):
  File "scan.py", line 12, in <module>
    elif(fin_scan_resp.haslayer(TCP)):
AttributeError: 'NoneType' object has no attribute 'haslayer'

与 Xmas Tree 扫描类似,这里同样切换成 172.16.2.79 的本地地址。再次测试,结果如下:

Begin emission:
...Finished to send 1 packets.
.*
Received 5 packets, got 1 answers, remaining 0 packets
Closed

TCP 空扫描(Null)

# encoding=utf-8

import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import *

dst_ip = "180.97.33.107"  # 百度 IP
src_port = RandShort()
dst_port = 80

null_scan_resp = sr1(IP(dst=dst_ip)/TCP(dport=dst_port, flags=""), timeout=10)
if str(type(null_scan_resp)) == "":
    print("Open|Filtered")
elif null_scan_resp.haslayer(TCP):
    if null_scan_resp.getlayer(TCP).flags == 0x14:
        print("Closed")
elif null_scan_resp.haslayer(ICMP):
    if int(null_scan_resp.getlayer(ICMP以上是关于交互式数据包处理程序 Scapy 入门指南的主要内容,如果未能解决你的问题,请参考以下文章

Python渗透测试工具合集

scapy - 基于python的数据包操作库

Python渗透测试工具有哪些

Scapy 从入门到放弃

数据包处理利器——Scapy高级应用

Scapy 数据包嗅探器触发对每个嗅探数据包的操作