ARP 协议(地址解析协议)属于数据链路层的协议,主要负责根据网络层地址(IP)来获取数据链路层地址(MAC) 以太网协议规定,同一局域网中的一台主机要和另一台主机进行直接通信,必须知道目标主机的 MAC 地址。而在 TCP/IP 中,网络层只关注目标主机的 IP 地址。这就导致在以太网中使用 IP 协议时,数据链路层的以太网协议接收到的网络层 IP 协议提供的数据中,只包含目标主机的 IP 地址。于是需要 ARP 协议来完成 IP 地址到 MAC 地址的转换
这里假设一个以太网的结构图,如下图所示
在这里假设 PC1 想和 PC3 进行通信,且知道了 PC3 的 IP 地址为 192.168.1.3,但是根据网络层的 IP 无法进行通信,根据以太网的协议规定,两台主机想要进行直接通信必须知道对方的 MAC 地址
步骤如下:
1.PC1 会检查自己的 ARP 缓存表中该 IP 是否有对应的 MAC 地址,若有可以直接进行通信
2. 若无则 PC1 就会使用以太网广播包来给网络上的每一台主机发送 ARP 请求,询问 192.168.1.3 的 MAC 地址,同时 ARP 请求中也包含了 PC1 自己的 IP 地址和 MAC 地址,以太网内的所有主机都会接收到 ARP 请求,并检查是否与自己的 IP 地址相符合。如果不符合则丢弃该 ARP 请求
3.PC3 确定 ARP 请求中的 IP 与自己的 IP 相符合后,就将 ARP 请求中的 PC1 的 IP 地址与 MAC 地址添加到本地的 ARP 缓存中
4.PC3 将自己的 MAC 地址发送给 PC1
5.PC1 收到 PC3 的 ARP 相应,将 PC3 的 IP 地址和 MAC 地址都更新到本地的 ARP 缓存中
这样就可以进行直接通信了,用一个通俗点的方式来概括说明一下就是:
你(PC1)知道了网友(PC3)的网名或者是 QQ 号(IP 地址),你们打算参加一个网友见面会来面基,在这个活动上你就拿着该网名(IP 地址)去挨个询问(发送 ARP 请求)参加的网友,看看谁是你想要面基的人,不认识的人就否认你的询问,直到问道了网友(PC3),他按照自己的网名知道了自己就是你想寻找的那个人,于是他就把你的网名和名字(MAC 地址)都对应记了下来,并且告诉你了他的名字,你也记下了他的名字,更新了备注 本地 ARP 缓存表是有生存周期的,生存周期结束后,就会再重复上面的过程
当目标主机与我们共同处于一个以太网时,利用 ARP 进行主机发现是一个最好的选择,这种方式快且精准。同样的我们借助 Scapy 来编写 ARP 主机发现的脚本:
通过脚本对以太网内的每个主机都进行 ARP 请求。若主机存活,则会响应我们的 ARP 请求,否则不会响应。因为 ARP 涉及到网络层和数据链路层,因此需要用到 Scapy 中的 Ether 和 ARP 库中的 Eher 和 ARP 参数如下所示
Ether 中的 dst 代表目标 MAC 地址,src 代表源 MAC 地址。ARP 中的 op 代表消息类型,1 为 ARP 请求,2 为 ARP 响应,hwsrc 和 psrc 分别代表源 MAC 地址和源 ip 地址,hwdst 和 pdst 分别代表的是目标 MAC 地址和目标 ip 地址
导入模块
1 2 3 4 import os import re import optparse from scapy.all import *
编写本机 IP 地址和 MAC 地址获取函数,通过正则表达式来进行获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 取IP地址和MAC地址函数 def HostAddress(iface): # os.open执行后返回执行结果 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 探测函数,根据本机的 IP 地址和 MAC 地址信息,自动生成目标进行探测并把结果写入文件
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 #ARP扫描函数 def ArpScan(iface='eth0'): #通过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的二层和三层,所以要写成Rther/ARP 因为最底层用到了二层,所以要用srp()发包 ''' 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) #读取result中的应答包和应答包内容 resultAns = result[0].res #存活主机列表 liveHost = [] #number 为接收到应答包的总数 number = len(resultAns) print("=========================") print("ARP探测结果") print("本机ip地址:"+ip) print("本机mac地址:"+mac) print("=========================") 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("============================") #把存活主机IP写入文件 resultFile = open("result","w") for i in range(len(liveHost)): resultFile.write(liveHost[i][0]+"\n") resultFile.close()
编写 main 函数,利用 optparse 模块生成命令行参数化形式
1 2 3 4 5 6 if __name__ == '__main__': parser = optparse.OptionParser('usage:python %prog -i interfaces \n\n''Example:python %prog -i eth0\n') #添加网卡参数 parser.add_option('-i','--iface',dest = 'iface',default='eth0',type='string',help='interfaces name') (options,args) = parser.parse_args() ArpScan(options.iface)
提示:普通用户运行时需要 sudo,不然会出现 Operation not permitted 的提醒