主动信息搜集:基于ARP的主机发现
ARP协议(地址解析协议)属于数据链路层的协议,主要负责根据网络层地址(IP)来获取数据链路层地址(MAC)。以太网协议规定,同一局域网中的一台主机要和另一台主机进行直接通信,必须知道目标主机的MAC地址。而在TCP/IP中,网络层只关注目标主机的IP地址。这就导致在以太网中使用IP协议时,数据链路层的以太网协议收到的网络层协议提供的数据中,只包含目的主机的IP地址。于是需要ARP协议来完成IP地址到MAC地址到转换。如下图的以太网(Ethernet)结构图:
在上述以太网结构中,假设PC1想与PC3通信,步骤如下:
- PC1知道PC3的IP地址为192.168.68.150,然后PC1会检查自己的ARP缓存表中该IP是否有对应的MAC地址。
- 如果有,则进行通信。如果没有,PC1就会使用以太网广播包来给网络上的每一台主机发送ARP请求,询问192.168.68.128的MAC地址。ARP请求中同时也包含了PC1的IP地址和MAC地址。以太网的所有主机都会接收到ARP请求,并检查是否与自己的IP地址匹配。如果不匹配,则丢弃该ARP请求。
- PC3确定ARP请求中的IP地址与自己的IP地址匹配,则将ARP请求中PC1的IP地址和MAC地址添加到本地ARP缓存中。
- PC3将自己的MAC地址发送给PC1。
- PC1收到PC3的ARP响应时,将PC3的IP地址和MAC地址都更新到本地ARP缓存表
本地缓存表是有生存周期的,生存周期结束后,将再次重复上面的过程。当目标主机和我们处于同一以太网的时候,利用ARP进行主机发现是一个最好的选择。这种扫描方式快且精准。通过Scapy编写的ARP主机发现脚本,通过脚本对以太网内的每个主机都进行ARP请求。若主机存活,则会响应我们的ARP请求,否则不会响应。
Scapy中的ARP参数如下图:
Scapy中的Ether参数如下图:
备注:图中Ether中src表示源MAC地址,dst表示目的MAC地址。ARP中op代表消息类型,1为ARP请求,2为ARP响应,hwsrc和psrc表示源MAC地址和源IP地址,pdst表示目的的IP地址。
运 行 环 境:
OS:macOS Monterey Version 12.3.1(英文版)
IDE: PyCharm 2020.1
现将程序源码分享如下:
#_*_coding:utf-8_*_
# 作者 :liuxiaowei
# 创建时间 :4/17/22 9:56 AM
# 文件 :基于ARP的主机发现.py
# IDE :PyCharm
'''
ARP协议(地址解析协议)属于数据链路层的协议,主要负责根据网络层地址(IP)来获取数据链路层地址(MAC)
当目标主机与我们处于同一以太网的时候,利用ARP进行主机发现是一个最好的选择
'''
import os
import re
import optparse
from scapy.all import *
# 取IP地址和MAC地址函数
def HostAddress(iface):
# os.popen执行后返回执行结果
ipData = os.popen('ifconfig ' + iface)
# 对ipData进行类型转换,再用正则进行匹配
dataLine = ipData.readlines()
# re.search利用正则匹配返回第一个成功匹配对结果,存在结果则为true
# 取MAC地址
if re.search('\w\w:\w\w:\w\w:\w\w:\w\w:\w\w', str(dataLine)):
# 取出匹配的结果
MAC = re.search('\w\w:\w\w:\w\w:\w\w:\w\w:\w\w', str(dataLine)).group(0)
# 取IP地址
if re.search(r'((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)', str(dataLine)):
IP = re.search(r'((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d)', str(dataLine)).group(0)
# 将IP和MAC通过元组的形式返回
addressInfo = (IP, MAC)
return addressInfo
# ARP扫描函数
def ArpScan(iface='en0'):
# 通过HostAddress返回的元组取出MAC地址
mac = HostAddress(iface)[1]
# 取出本机IP地址
ip = HostAddress(iface)[0]
# 对本机IP地址进行分隔并作为依据元素,用于生成需要扫描对IP地址
ipSplit = ip.split('.')
# 需要扫描的IP地址列表
ipList = []
# 根据本机IP生成IP扫描范围
for i in range(1, 255):
ipItem = ipSplit[0] + '.' +ipSplit[1] + '.'+ipSplit[2]+'.'+str(i)
ipList.append(ipItem)
'''
发送ARP包
因为要用到OSI的二层和三层,所以要写成Ether/ARP
因为最底层用到了二层,所以要用srp()发包
'''
print(ipList)
result = srp(Ether(src=mac, dst='FF:FF:FF:FF:FF:FF')/ARP(op=1, hwsrc=mac, hwdst='00:00:00:00:00:00', pdst=ipList), iface=iface,
timeout=2, verbose=False) # ipList赋值给pdst进行遍历
# 读取result中的应答包和应答包内容
resultAns = result[0].res
# 存活主机列表
liveHost = []
# number为接收到应答包的总数
number = len(resultAns)
print('=' * 35)
print('ARP 探测结果')
print('本机IP地址:' + ip)
print('本机MAC地址:' + mac)
print('=' * 35)
for x in range(number):
IP = resultAns[x][1][1].fields['psrc']
MAC = resultAns[x][1][1].fields['hwsrc']
liveHost.append([IP, MAC])
print('IP:' + IP + '\n\n' + 'MAC:' + MAC )
print('=' * 35)
# 把存活主机IP写入文件
resultFile = open('result', 'w')
for i in range(len(liveHost)):
resultFile.write(liveHost[i][0] + '\n')
resultFile.close()
if __name__ == '__main__':
parser = optparse.OptionParser('usage: Python % prog -i interfaces \n\n'
'Example: Python %prog -i en0\n')
# 添加网卡参数 -i
parser.add_option('-i', '--iface', dest='iface', default='en0', type='string', help='interfaces name')
(options, args) = parser.parse_args()
ArpScan(options.iface)
运行结果如下图: