一:UDP协议是非面向连接的协议,不同于TCP扫描依赖于建立连接过程,因此UDP扫描是不可靠的
udp主机扫描原理:利用ICMP端口不可达报文进行扫描
当发送一个udp数据包到主机的某个关闭端口上时,目的主机会返回一个ICMP包指示目标端口不可达,这样意味着主机是存活的
优点:可以完成对UDP端口的探测。
缺点:需要系统管理员的权限。扫描结果的可靠性不高。因为当发出一个UDP数据报而没有收到任何的应答时,有可能因为这个UDP端口是开放的,也有可能是因为这个数据报在传输过程中丢失了,所以要挑选一个不太可能被使用的端口进行探测。另外,扫描的速度很慢。原因是在RFC1812的中对ICMP错误报文的生成速度做出了限制。例如Linux就将ICMP报文的生成速度限制为每4秒钟80个,当超出这个限制的时候,还要暂停1/4秒。
代码如下:
import socket
import os
#监听的主机
host="192.168.1.11"#创建原始套接字 然后绑定在公开接口上if os.name=="nt":
socket_protocol=socket.IPPROTO_IPelse:
socket_protocol=socket.IPPROTO_TCMP
sniffer=socket.socket(socket.AF_INET,socket.SOCK_RAW,socket_protocol)
sniffer.bind((host,0))
#设置在捕获的数据包中包含ip头
sniffer.setsockopt(socket.IPPROTO_IP,socket.IP_HDRINCL,1)
#如果操作系统是windows 需要将IOCTL设置为混杂模式 混杂模式即为抓取流经网卡的所有数据包if os.name=="nt":
sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_ON)
#读取单个数据包
print sniffer.recvfrom(65565)if os.name=="nt":
sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_OFF)
先读取单个数据包 看返回的是什么
当前模式下,可以嗅探到任何高层协议例如UDP TCP ICMP 的所有IP头信息,但是很明显,完全看不懂,因此需要对返回的数据包进行解码
UDP是基于IP协议之上的,ICMP同样是基于IP协议之上,所以要先对IP头进行解析 下图所示为IP头部分
IP头结构:
wireshark抓包结果 图中所示部分为ping www.baidu.com时抓到的icmp包的IP头部
IP头的数据结构为:
class IP(Structure):
_fields_ = [
("ihl", c_ubyte, 4), #4位首部长度
("version", c_ubyte, 4), #4位IP版本号
("tos", c_ubyte), #8位服务类型
("len", c_ushort), #16位IP包总长度
("id", c_ushort), #16位标识,用于辅助IP包的拆装
("offset", c_ushort), #3位标志位加13位偏移位
("ttl", c_ubyte), #8位IP包生存时间
("protocol_num", c_ubyte), #8位协议号
("sum", c_ushort), #16位IP首部校验和
("src", c_uint), #32位源地址
("dst", c_uint), #32位目的地址
]
对IP头进行解码:
import socket
import os
importstruct
from ctypes import *host="192.168.1.11"
classIP(Structure):
_fields_=[
("ihl", c_ubyte, 4),
("version", c_ubyte, 4),
("tos", c_ubyte),
("len", c_ushort),
("id", c_ushort),
("offset", c_ushort),
("ttl", c_ubyte),
("protocol_num", c_ubyte),
("sum", c_ushort),
("src", c_uint),
("dst", c_uint),
]
#使用from_buffer_copy方法在__new__方法将收到的数据生成一个IP class的实例
def __new__(self,socket_buffer=None):returnself.from_buffer_copy(socket_buffer)
def __init__(self,socket_buffer=None):
#协议字段与协议名称相对应
self.protocol_map={1:"ICMP",6:"TCP",17:"UDP"}
#使用了python struct库的pack方法 用指定的格式化参数将src 和dst的long型数值转换为字符串,然后使用socket.inet_ntoa方法将字符串的一串数字转换为对应的ip格式
self.src_address=socket.inet_ntoa(struct.pack("
self.dst_address= socket.inet_ntoa(struct.pack("
#协议类型try:
self.protocol=self.protocol_map[self.protocol_num]
except Exception,e:
self.protocol=str(self.protocol_num)
#创建一个socket并绑定到公共接口if os.name=="nt":
socket.protocol=socket.IPPROTO_IPelse:
socket.protocol=socket.IPPROTO_ICMP
sniffer=socket.socket(socket.AF_INET,socket.SOCK_RAW,socket.protocol)
sniffer.bind((host,0))
#设置捕获的数据包中包含ip头
sniffer.setsockopt(socket.IPPROTO_IP,socket.IP_HDRINCL,1)
#如果操作系统是windows 需要将IOCTL设置为混杂模式 混杂模式即为捕获经过网卡的所有数据包if os.name=="nt":
sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_ON)
#获取数据包并对ip头进行解码try:whileTrue:
#读取单个数据包 数据包为数组 第一个元素为要解码的数据
raw_buffer=sniffer.recvfrom(65565)[0]
#缓冲区前20个字节作为ip头解码
ip_head=IP(raw_buffer[0:20])
#输出协议类型和通信双方ip地址
print"Protocol:%s %s->%s" %(ip_head.protocol,ip_head.src_address,ip_head.dst_address)
except KeyboardInterrupt:
# 如果运行在windows 则关闭混杂模式if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
IP协议并不是一个可靠的协议,它不保证数据被送达,所以,保证数据送达的工作应该由其他的模块来完成。其中一个重要的模块就是ICMP(网络控制报文)协议。
当传送IP数据包发生错误--比如主机不可达,路由不可达等等,ICMP协议将会把错误信息封包,然后传送回给主机。给主机一个处理错误的机会,这 也就是为什
么说建立在IP层以上的协议是可能做到安全的原因。ICMP数据包由8bit的错误类型和8bit的代码和16bit的校验和组成。而前 16bit就组成了ICMP所要传递的信息。ICMP
数据报报文被封装在IP数据报内部进行传输
ICMP头部数据结构
图下所示为wires hark抓包ping www.baidu.com的icmp包头部
#icmp头部数据结构
classICMP(Structure):
_fields_=[
("type", c_ubyte), #类型
("code", c_ubyte), #代码值
("checksum", c_ushort), #校验和
("unused", c_ushort), #未使用
("next_hop_mtu", c_ushort) #下一跳
]
对ICMP头部进行解析:
icmp头部type值所代表的含义:
8bits类型和8bits代码字段:一起决定了ICMP报文的类型。常见的有:
类型8、代码0:回射请求。
类型0、代码0:回射应答。
类型11、代码0:超时。
常见的不可到达类型还有网络不可到达(Code=0)、主机不可到达(Code=1)、协议不可到达(Code=2)等
需要找到type 为3 (这种数据包意味着目标不可达)code为3的ICMP数据包 (代码值为3则代表目标主机产生了端口不可达的错误)
import socket
import os
importstruct
from ctypes import *host="192.168.1.11"
classIP(Structure):
_fields_=[
("ihl", c_ubyte, 4),
("version", c_ubyte, 4),
("tos", c_ubyte),
("len", c_ushort),
("id", c_ushort),
("offset", c_ushort),
("ttl", c_ubyte),
("protocol_num", c_ubyte),
("sum", c_ushort),
("src", c_uint),
("dst", c_uint)
]
#使用from_buffer_copy方法在__new__方法将收到的数据生成一个IP class的实例
def __new__(self,socket_buffer=None):returnself.from_buffer_copy(socket_buffer)
def __init__(self,socket_buffer=None):
#协议字段与协议名称相对应
self.protocol_map={1:"ICMP",6:"TCP",17:"UDP"}
#使用了python struct库的pack方法 用指定的格式化参数将src 和dst的long型数值转换为字符串,然后使用socket.inet_ntoa方法将字符串的一串数字转换为对应的ip格式
self.src_address=socket.inet_ntoa(struct.pack("
self.dst_address= socket.inet_ntoa(struct.pack("
#协议类型try:
self.protocol=self.protocol_map[self.protocol_num]
except Exception,e:
self.protocol=str(self.protocol_num)
#解码icmpclassICMP(Structure):
_fields_=[
("type", c_ubyte), #类型
("code", c_ubyte), #代码值
("checksum", c_ushort), #校验和
("unused", c_ushort), #未使用
("next_hop_mtu", c_ushort) #下一跳
]
#使用from_buffer_copy方法在__new__方法将收到的数据生成一个IP class的实例
def __new__(self,socket_buffer=None):returnself.from_buffer_copy(socket_buffer)
def __init__(self,socket_buffer=None):
pass
#创建一个socket并绑定到公共接口if os.name=="nt":
socket.protocol=socket.IPPROTO_IPelse:
socket.protocol=socket.IPPROTO_ICMP
sniffer=socket.socket(socket.AF_INET,socket.SOCK_RAW,socket.protocol)
sniffer.bind((host,0))
#设置捕获的数据包中包含ip头
sniffer.setsockopt(socket.IPPROTO_IP,socket.IP_HDRINCL,1)
#如果操作系统是windows 需要将IOCTL设置为混杂模式 混杂模式即为捕获经过网卡的所有数据包if os.name=="nt":
sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_ON)
#获取数据包并对ip头进行解码try:whileTrue:
#读取单个数据包 数据包为数组 第一个元素为要解码的数据
raw_buffer=sniffer.recvfrom(65565)[0]
#缓冲区前20个字节作为ip头解码
ip_head=IP(raw_buffer[0:20])
# print ip_head.ihl
#输出协议类型和通信双方ip地址
print"Protocol:%s %s->%s" %(ip_head.protocol,ip_head.src_address,ip_head.dst_address)
#如果协议类型为icmpif ip_head.protocol=="ICMP":
#计算ICMP包起始位置
offset=ip_head.ihl * 4buf=raw_buffer[offset:offset+sizeof(ICMP)]
#解析icmp
icmp_header=ICMP(buf)
# print icmp_header
print"ICMP -> Type: %d Code: %d" %(icmp_header.type, icmp_header.code)
#处理异常 ctrl+c
except KeyboardInterrupt:
# 如果运行在windows 则关闭混杂模式if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
解析结果:
下面实现对整个内网进行主机扫描 需要用到netaddr模块
windows下安装netaddr:pip install netaddr
实现代码:
import threading
import time
import socket
import os
importstruct
from ctypes import *
fromnetaddr import IPNetwork,IPAddress
#监听的主机
host="192.168.1.11"#扫描的子网
subnet="192.168.1.0/24"#自定义字符串 在icmp包中进行核对
message="PYTHONTEST"#批量发送udp数据
def udp_sender(subnet,message):
time.sleep(1)
sender=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)for ip inIPNetwork(subnet):try:
sender.sendto(message,("%s" % ip,32000))
except Exception,e:
print eclassIP(Structure):
_fields_=[
("ihl", c_ubyte, 4),
("version", c_ubyte, 4),
("tos", c_ubyte),
("len", c_ushort),
("id", c_ushort),
("offset", c_ushort),
("ttl", c_ubyte),
("protocol_num", c_ubyte),
("sum", c_ushort),
("src", c_uint),
("dst", c_uint)
]
#使用from_buffer_copy方法在__new__方法将收到的数据生成一个IP class的实例
def __new__(self,socket_buffer=None):returnself.from_buffer_copy(socket_buffer)
def __init__(self,socket_buffer=None):
#协议字段与协议名称相对应
self.protocol_map={1:"ICMP",6:"TCP",17:"UDP"}
#使用了python struct库的pack方法 用指定的格式化参数将src 和dst的long型数值转换为字符串,然后使用socket.inet_ntoa方法将字符串的一串数字转换为对应的ip格式
self.src_address=socket.inet_ntoa(struct.pack("
self.dst_address= socket.inet_ntoa(struct.pack("
#协议类型try:
self.protocol=self.protocol_map[self.protocol_num]
except Exception,e:
self.protocol=str(self.protocol_num)
#解码icmpclassICMP(Structure):
_fields_=[
("type", c_ubyte), #类型
("code", c_ubyte), #代码值
("checksum", c_ushort), #校验和
("unused", c_ushort), #未使用
("next_hop_mtu", c_ushort) #下一跳
]
#使用from_buffer_copy方法在__new__方法将收到的数据生成一个IP class的实例
def __new__(self,socket_buffer=None):returnself.from_buffer_copy(socket_buffer)
def __init__(self,socket_buffer=None):
pass
#创建一个socket并绑定到公共接口if os.name=="nt":
socket.protocol=socket.IPPROTO_IPelse:
socket.protocol=socket.IPPROTO_ICMP
sniffer=socket.socket(socket.AF_INET,socket.SOCK_RAW,socket.protocol)
sniffer.bind((host,0))
#设置捕获的数据包中包含ip头
sniffer.setsockopt(socket.IPPROTO_IP,socket.IP_HDRINCL,1)
#如果操作系统是windows 需要将IOCTL设置为混杂模式 混杂模式即为捕获经过网卡的所有数据包if os.name=="nt":
sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_ON)
#开始发包
t=threading.Thread(target=udp_sender,args=(subnet,message))
t.start()
#处理数据try:
i=1
whileTrue:
#读取单个数据包 数据包为数组 第一个元素为要解码的数据
raw_buffer=sniffer.recvfrom(65565)[0]
#缓冲区前20个字节作为ip头解码
ip_head=IP(raw_buffer[0:20])
# print ip_head.ihl
#输出协议类型和通信双方ip地址
# print"Protocol:%s %s->%s" %(ip_head.protocol,ip_head.src_address,ip_head.dst_address)
#如果协议类型为icmpif ip_head.protocol=="ICMP":
#计算ICMP包起始位置
offset=ip_head.ihl * 4buf=raw_buffer[offset:offset+sizeof(ICMP)]
#解析icmp
icmp_header=ICMP(buf)
# print icmp_header
# print"ICMP -> Type: %d Code: %d" %(icmp_header.checksum, icmp_header.unused)
#检查类型和代码值是否为3if icmp_header.type==3 and icmp_header.code==3:
#确认响的主机在目标子网内if IPAddress(ip_head.src_address) inIPNetwork(subnet):
#确认ICMP包中包含我们发送的自定义的字符串if raw_buffer[len(raw_buffer)-len(message):] ==message:
#输出存活主机
print"%d Host Up: %s" %(i,ip_head.src_address)
#处理异常 ctrl+c
except KeyboardInterrupt:
# 如果运行在windows 则关闭混杂模式if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
扫描结果:由于我直接在本机测试的 所以只有一个存活主机