概述
ARP,全称是Address Resolution Protocal,即地址解析协议,是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到局域网络上的所有主机,并接受返回消息,以此确定目标的物理地址;收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。地址解析协议是建立在网络中各个主机互相信任的基础上的,局域网络上的主机可以自主发送ARP应答消息。地址解析协议是IPv4中必不可少的协议。
OSI模型把网络工作分为七层,IP地址在OSI模型的第三层,MAC地址在第二层,彼此不直接打交道。在通过以太网发送IP数据包时,需要先封装第三层(32位IP地址)、第二层(48位MAC地址)的报头,但由于发送时只知道目标IP地址,不知道其MAC地址,又不能跨第二、三层,所以需要使用地址解析协议。使用地址解析协议,可根据网络层IP数据包包头中的IP地址信息解析出目标硬件地址(MAC地址)信息,以保证通信的顺利进行。来源百度百科ARP
工作过程
arp数据报格式
扩展
当一台主机把以太网数据帧发送到位于同一局域网上的另一台主机时,是根据48bit的以太网地址来确定目的接口的。设备驱动程序从不检查IP数据报中的目的IP地址。ARP适用于多路链路,网络接口有一个硬件地址(一个48bit的值,标识不同的以太网或令牌环网络接口)。在硬件层次上进行的数据帧交换必须有正确的接口地址。但是TCP/IP有自己的地址:32bit的IP地址。知道主机的IP地址并不能让内核发送一帧数据给主机。以太网驱动程序必须知道目的端的硬件地址才能发送数据。ARP的功能是在32bit的IP地址和采用不同网络技术的硬件地址之间提供动态映射。对于点对点链路,ARP并不适用。
如果目标主机在本地子网中,ARP将解析到目标主机的MAC地址;如果不在本地子网中,则ARP解析到的MAC地址是默认网关的MAC地址(比如路由器的MAC地址)
Linux系统命令
arp -a查看本地缓存mac记录
arp -d 主机 删除一条主机mac缓存记录
备注:
当用远程终端工具连接到虚拟机Linux,那么每执行一次命令,都与linux建立一次通信,而Linux都会检查缓存中是否有windows主机的mac地址,如果没有则创建缓存。包括执行arp -d命令,相当于是删除掉之前的arp缓存记录之后,又新建一条记录。如上图,如果192.168.149.1是windows主机,而又要在执行arp -d192.168.149.1之后,使用arp -a查看执行结果,则不能使用远程终端,而需要在linux系统的终端执行。
使用scapy发送ARP请求
python代码一
from scapy.all import *
localmac="00:0c:29:c9:64:cf"
localip="192.168.149.130"
destip="192.168.149.1"
ifname="ens33"
result_raw=srp(Ether(src=localmac,dst='FF:FF:FF:FF:FF:FF')/ARP(op=1,hwsrc=localmac,hwdst='00:00:00:00:00:00',psrc=localip,pdst=destip),iface=ifname,timeout=1,verbose=False)
print(result_raw)
print(result_raw[0])
print(result_raw[0].res)
print(result_raw[0].res[0][1])
print(result_raw[0].res[0][1].getlayer(ARP).fields)
>>
(<Results: TCP:0 UDP:0 ICMP:0 Other:1>, <Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)
<Results: TCP:0 UDP:0 ICMP:0 Other:1>
[(<Ether dst=FF:FF:FF:FF:FF:FF src= 00:0c:29:c9:64:cf type=ARP |<ARP op=who-has hwsrc= 00:0c:29:c9:64:cf psrc=192.168.149.130 hwdst=00:00:00:00:00:00 pdst=192.168.149.1 |>>, <Ether dst=00:0c:29:c9:64:cf src=00:50:56:c0:00:08 type=ARP |<ARP hwtype=0x1 ptype=IPv4 hwlen=6 plen=4 op=is-at hwsrc=00:50:56:c0:00:08 psrc=192.168.149.1 hwdst=00:0c:29:c9:64:cf pdst=192.168.149.130 |<Padding load='\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |>>>)]
b'\x00\x0c)\xc9d\xcf\x00PV\xc0\x00\x08\x08\x06\x00\x01\x08\x00\x06\x04\x00\x02\x00PV\xc0\x00\x08\xc0\xa8\x95\x01\x00\x0c)\xc9d\xcf\xc0\xa8\x95\x82\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
{'hwtype': 1, 'ptype': 2048, 'hwlen': 6, 'plen': 4, 'op': 2, 'hwsrc': '00:50:56:c0:00:08', 'psrc': '192.168.149.1', 'hwdst': '00:0c:29:c9:64:cf', 'pdst': '192.168.149.130'}
备注:
1.result_raw是一个元组,元组的元素是scapy.plist.SndRcvList类类型
2.在二层发送包需要制定接口,即iface=网卡名
3.构造请求包可以简化为:
result_raw=srp(Ether(src=localmac,dst='FF:FF:FF:FF:FF:FF')/ARP(pdst=destip),iface=ifname,timeout=1,verbose=False)
简单的局域网ARP扫描
表示IP范围
操作系统中实现网络栈的具体逻辑是通过结合IP地址和掩码来表示子网。掩码指出了某主机属于某子网所需要的高位比特匹配数。IP地址的每字节表示8位二进制数。
127.0.0.0/8:此模式描述了前面所述的预留给本机的IP地址段,该模式指出地址的前8位(1字节)必须与127匹配,余下的24位(3字节)则可以是任意值。
192.168.0.0/16:此模式匹配了属于192.168私有地址段的任何IP地址,因为它指出前16位必须完全匹配,而这个32位地址的后16位则可以是任意值。
192.168.5.0/24:这里明确指定了一个特定的独立子网。这可能是整个互联网上最常见的子网掩码。地址的前3字节都被明确指定了,用来匹配属于该子网的IP地址。属于该子网的机器只有在最后1字节(最后8位)不同。这就允许有256个不同的地址。通常来说,.0地址用来表示子网名,.255地址则用作“广播数据包”的目标地址,“广播数据包”会被发送到子网内所有主机。这样,就有254个地址可以随意分配给计算机。.1地址通常用于连接外网的网关,但有些公司和学校也会选择其他地址。(Brandon Rhodes&John Goerzen《Python网络编程(第三版)》
为了扫描实现局域网ARP扫描,可以这样创建一组数据包
packet=Ether(dst="ff:ff:ff:ff:ff:ff",src="00:0c:29:c9:64:cf")/ARP(pdst="192.168.149.0/24")
那这个程序就很简单了
from scapy.all import *
localmac=" 00:0c:29:c9:64:cf"
destip="192.168.149.0/24"
ifname="ens33"
result_raw=srp(Ether(src=localmac,dst='FF:FF:FF:FF:FF:FF')/ARP(pdst=destip),iface=ifname,timeout=1,verbose=False)
#print(result_raw)
#print(result_raw[0].res)
for i in result_raw[0].res:
print(i[0][1].getlayer(ARP).fields)
>>
{'pdst': '192.168.149.1'}
{'pdst': '192.168.149.2'}
{'pdst': '192.168.149.254'}
好吧,输出的目标ip,但是逻辑没有错。改成这样吧还是:
for i in result_raw[0].res:
print(i[1].getlayer(ARP).fields)
>>
{'hwtype': 1, 'ptype': 2048, 'hwlen': 6, 'plen': 4, 'op': 2, 'hwsrc': '00:50:56:c0:00:08', 'psrc': '192.168.149.1', 'hwdst': '00:0c:29:c9:64:cf', 'pdst': '192.168.149.130'}
{'hwtype': 1, 'ptype': 2048, 'hwlen': 6, 'plen': 4, 'op': 2, 'hwsrc': '00:50:56:f4:a2:f4', 'psrc': '192.168.149.2', 'hwdst': '00:0c:29:c9:64:cf', 'pdst': '192.168.149.130'}
{'hwtype': 1, 'ptype': 2048, 'hwlen': 6, 'plen': 4, 'op': 2, 'hwsrc': '00:50:56:ea:4e:ba', 'psrc': '192.168.149.254', 'hwdst': '00:0c:29:c9:64:cf', 'pdst': '192.168.149.130'}
这样就对了。当然这个程序可以改进一下:
result=[]
for res in result_raw[0].res:
ip=res[1].getlayer(ARP).fields["psrc"]
mac=res[1].getlayer(ARP).fields["hwsrc"]
result.append((ip,mac))
for ip,mac in result:
print(ip+"->"+mac);
>>
192.168.149.1->00:50:56:c0:00:08
192.168.149.2->00:50:56:f4:a2:f4
192.168.149.254->00:50:56:ea:4e:ba
还有一个写法:
ans,unans=srp(Ether(src=localmac,dst='FF:FF:FF:FF:FF:FF')/ARP(pdst=destip),iface=ifname,timeout=1,verbose=False)
result=[]
for s,r in ans.res: #for s,r in ans
result.append((r[ARP].psrc,r[ARP].hwsrc))
for ip,mac in result:
print(ip+"->"+mac);