一: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)

扫描结果:由于我直接在本机测试的 所以只有一个存活主机