编写基于udp的主机发现工具
目的:发现一个网络内有多少存活主机
原理:如果目标主机udp口没开一般会返回一个icmp响应,收到这个响应则表明目标主机存活。如果开udp口了则可能无响应,无法判断主机是否存活。选udp是开销较小
setsocket是抓包时包含包的IP头
当判断是windows系统时,会开闭网卡混杂模式,通过socket.ioctl方法实现开闭
验证效果如下图,先开一个窗口执行该脚本,则机器作为服务器运行,在其他窗口ping这个机器,服务器会打印详细信息
查看ip层
抓包的ip头是编码过的,无法直接看出其含义,可通过解码查看ip头具体信息
ip头为二进制,ip头是有结构的,解码需要解码对应的数据结构,具体 可通过py的ctypes或struct等库实现
典型ipv4头结构:
ctypes
struct
struct.unpack用了<BBHHHBBH4s4s,其中B表示1字节(unsigned char),H表示2字节(unsigned short),s表示字节数组,用s时前面需要带上数字,比如4s表示长度为4的字节数组
self.ver版本向后位移是因为ip头用struct映射为ver在一个字节的高四位,需要往后移四位
self.ihl与0xF与运算是为了消除一个字节八位里高四位的版本信息
icmp不需要这样的位运算,是因为icmp头信息长度基本是8的倍数,无需位运算
编写IP解码器
class为struct的类,后面的代码如下
raw_buffer[:20]是因为ip头默认参数长度为20Bytes
在虚机上执行脚本看下效果,在windows上ping虚机ip
发现只打印了ICMP信息,再看下windows侧执行脚本
小结
socket.socket
第二个参数为socket type,默认socket.SOCK_STREAM,文档写了,常用socket.SOCK_STREAM和socket.SOCK_DGRAM
socket — Low-level networking interface — Python 3.12.3 documentation
socket.socket.setsockopt
socket.socket.setsockopt为在某个level设置option的值为value
这个应该表示在socket.IPPROTO_IP level设置是否使用原始套接字为1(True),表示使用自定义IP头
socket.socket.ioctl
解码ICMP
注意到sniff函数多了对icmp头的判断,是通过ip头的协议类型判断是否icmp类型,然后通过字节位移获取icmp头部信息,此处逻辑可能为发icmp包先装icmp头,再在icmp头前面装ip头,所以获取icmp头时需要加上ip头的位移。ip头表示IP头长度的ihl(internet header length)单位为32比特,即4字节,需要乘以4,ihl用4位二进制表示。获取到icmp头后,用struct映射数据结构。那为什么获取ip头直接取前20个字节?因为前20个是必选参数,估计可选参数会占用一些空间,用ihl计算icmp位移估计是把ip头可选参数跳过算得准
进行校验,在虚机运行脚本,然后在宿主机ping虚机ip
给上面代码添加子网群发udp代码:
main函数中初始化scanner时,创建了server端监听socket
udp_sender中创建socket给子网内所有主机发消息,注意ipaddress.ip_network(subnet).hosts()是拼写出来的子网的所有主机,比如给ip_network方法传入的subnet参数是192.168.99.1/24,返回的结果是192.168.99.1, 192.168.99.2, 192.168.99.3, ...,254个ip地址,即子网下所有的主机地址。socket创建后通过socket发消息,调用socket的sendto方法,不需要显式连接目标ip,直接将目标ip作为参数传入sendto方法即可
嗅探逻辑为通过子网里的一个主机给子网所有ip地址发消息,如果主机活着会发一个icmp响应,这个响应的源ip地址即为活着的主机ip,看下效果
发现探测到子网内只有我们一个虚机就是运行脚本的虚机
注意给ipaddress.ip_network传入子网参数时,子网后面的位数一定要是0,不然报错
小结
socket.socket.sendto
其他
socket.recvfrom返回的是字节数组
socket.recvfrom返回的报文的数据在报文最末尾
可通过python库ipaddress判断某子网内是否有某个主机ip:ipaddress.ip_address(ipaddr) in ipaddress.IPv4Network(subnet_addr)
socket发udp时,仅需要在发数据时传入目标ip地址,不需要socket.connect,只有两步1初始化socket2调socket.sendto方法发udp请求,如果发tcp的话需要socket.connect其他和udp都一样