ARP(地址解析协议)是一种把物理地址地址转换成逻辑地址的通讯协议,它属于TCP/IP协议栈中的数据链路层协议。下图是Wiki上的一副截图
所以它只有两层数据包头——以太网头、ARP头(TCP协议有三层,以太网头、IP头、TCP头;HTTP协议有四层,以太网头、IP头、TCP头、HTTP头)。
- 以太网头包括6bytes的目标地址,6bytes的源地址,2bytes数据帧类型(对于ARP来说它是0x0806)
- ARP头部包括2bytes的物理地址类型(0x0001以太网,代表MAC地址),2bytes逻辑地址类型(0x8000 IP地址);6bytes表示硬件地址长度(MAC地址为固定值6),4bytes表示逻辑地址长度(IP地址为固定值4);1bytes操作码(1表示请求,2表示回复)
- 后面是可以变长的发送者物理地址、逻辑地址;目标硬件地址、逻辑地址。
我一直使用物理地址、逻辑地址这两个术语;ARP是一种地址解析协议所以它可以实现任何两种地址之间的转换。只不过我们常用的是MAC地址转换成IP地址。
首先用tcpdump抓ARP包。(tcpdump -i en0 -xx -vvvv -n -e arp,希望你还记得这个最常用的选项。e选项表示输出以太网头部,-xx表示输出以太网16进制头部)
- ffff ffff ffff(6bytes),广播地址。说明ARP是通过广播的方式发送ARP请求的
- 60f8 1dac 8904(6bytes),本机MAC地址。
- 0806,表示ARP数据包
- 0001固定值表示物理地址是MAC地址
- 0800固定值表示逻辑地址是IP地址
- 06表示物理地址6bytes
- 04表示逻辑地址4bytes
- 0001表示是ARP请求
- 60f8 1dac 8904表示发送方物理地址,这里是发送方MAC地址
- c0a8 1fe9 表示发送方逻辑地址,这里是发送方的IP地址(192.168.31.233)
- 0000 0000 0000表示接收方物理地址,这里是接收方的MAC地址。全0表示广播地址
- c0a8 1f01表示接收方的逻辑地址,这里是想要查询的IP地址
第二条数据包ARP回应,大家可以自行解读。
ARP攻击的原理
通过上面的数据包分析不难看出,ARP协议是非常天真的协议——发送ARP请求->解读ARP回应。PC1喊一句——谁有IP地址192.168.31.1,快点把MAC地址发给我;如果PC2拥有这个IP地址则回应ARP请求。但是这只是理想情况,事实上一个没有拥有IP地址192.168.31.1的机器也是可以发送回应包的。如果PC1信以为真那么就会拿到错误的“IP地址<->MAC地址”关系。这就是ARP攻击。
从应用层面看机器之间通讯是基于IP地址的,但是操作系统在发送数据包的时候会在IP头部加上以太网头。这是由于以太网规范所决定的——数据包在网络上传送使用的地址都是物理地址。错误“IP<->MAC映射”会导致“数据包不可达”——通俗的说“连不上对方”。如果这个IP地址刚好是网关,那就意味着——“断网”。
实施ARP攻击需要解决三个问题
- 探测到ARP请求
- 发送ARP回应数据包
- 请求者“应用”我们的回应包
探测ARP请求
以太网是“共享传输介质”,所以任何在网络上传递的数据包都可以被所有网卡探测到。为了减轻网卡的压力网卡设计的时候会只选择读取和自己MAC地址匹配的数据包(以太网数据包头目标地址=自己MAC地址)所以操作系统是无法读取“所有数据包”的。当然,这个开关是可以被关闭的——这就是混杂模式。在这种模式下所有的数据包都会被网卡接受并且传递到操作系统的TCP/IP协议栈。
ARP请求本身就是广播数据包所以我们打不打开"混杂模式"都可以弹出到ARP请求。我一般习惯开启混杂模式。
实现网络探测的库基本上都是脱胎于libpcap(包括tcpdump),这是一个非常历史悠久的库,我们直接上代码。
pcap_open_live第一个参数是网卡名字(比如:eth0),65545表示最大捕获65545bytes的数据包(其实我们永远到不了这个数字,一般1460或者1500就可以了),1表示开启混杂模式,0表示一直抓取数据;errbuf错误消息如果函数调用失败会把错误信息写入到这个字符串中。
pcap_loop第一个参数是pcap_open_live的返回值,它是一个结构体;第二个参数是抓取次数,-1表示一直抓取;on_pcaket是一个函数,每当抓取到数据包都会调用该函数(回调);最后一个参数是一个指针把共享数据传递到回调函数里面。
这个就是on_pcaket的原型(第一个参数是pcap_loop的最后一个参数),pkthdr表示抓取到数据包头指针;packet是数据包。
我们可以在on_packet里面直接判断packet是否为ARP请求
packet表示完整的数据包,所以它的开头一定是以太网数据包头。我们直接看结构体
就是以太网头的三个字段。
跳过以太网头一定是ARP头部,所以我们定义了指针arp_hdr。
发送ARP回应数据包
socket的封装提供了TCP和UDP两种接口,而我们要构造的ARP数据包不属于这两种所以只能通过RAW类型的socket来写入自己构造的数据包。我们使用libetnet来完成这个工作。
第一个参数告诉libnet,我们要直接构造数据链路层数据包——自己填充以太网头和ARP头;第二个参数是要发送数据包的网卡,第三个参数是错误处理。
构造一个ARPOP_REPLY类型的数据包(回应);mac_addr是自己的MAC地址;tpa是请求arp的目标地址(对于我们来说是源地址——即便我们不是192.168.31.1也是可以填写这个字段,系统不会校验这部分数据);后面的数据都可以通过以太网头和arp头填充。
最后加上以太网头
然后就可以发送了
请求者“应用”的回应包
PC1发送ARP请求,如果被我们的程序探测到并且我们“编造”一个ARP回应那么对于PC1来说它会收到两次ARP回应。一条是正确的ARP回应,一条是我们编造的ARP回应。
对于Windows来说它会选择最后到达的回应应用;对于Linux来说它会选择第一个到达的回应应用。(所以一些简单的ARP攻击Linux是天生免疫的,此处你可以尽情吐槽一下Windows的弱智)
所以如果我们要攻击Windows只需要sleep(1)再发送回应就行了;对于Linux来说就比较困难了——我们很难保证自己的数据包会优先到达。但是正所谓道高一次魔高一丈,我们不能欺骗Linux主机但是可以冒充Linux主机啊。简单来说就是:当网关发送ARP请求探测PC的时候我们可以冒充PC回应网关的请求,这也会到导致——断网。
尽情发挥吧
我只能提供一个简单的Demo,它会回应整个网络内所有的ARP请求(除了自己)。你可以加上一些判断语句实现“精准打击”只针对某个机器(比如知道它的IP地址或者MAC地址)。还可以发挥更多想象~~~
小心开车
ARP的防护办法其实很简单,以360的流量防火墙为例。它启动的时候会首先拿到当前网关的MAC地址,通过修改系统底层所有的ARP回应数据包都不会被真正的应用,这样就实现了“防”(你可以用MAC+IP地址绑定的方式固定网关,但是显的不够高大上)。
当收到ARP回应的时候360会比较自己保存的网关MAC地址,如果发现MAC地址发生了变化就认为发现了ARP攻击。最最致命的——无论你怎么伪造你都会暴漏自己的mac地址(回应ARP的时候头部以太网头的源地址是你自己的MAC地址)这是由于网卡本身的限制,如果mac地址不是自己的数据包是“发不出去的”。
完整代码
https://gist.github.com/fireflyc/796f55d54be39e629a2a1fcb2607a33b
总结
除了做坏事这种框架还可以做很多事情。它们的思路基本上都是一致的——探测数据包,通过应用处理,返回数据包。比如我们可以用DPDK(一种高性能的数据平面开发工具包,彻底无视Linux和TCP/IP协议栈)来探测数据包,通过移植TCP/IP协议到用户空间来绕过操作系统(kernel bypass)。
经过多年的努力我们已经解决了C10k的问题,现在又有人提出来C10M问题。基本上很多人的观点都基于kernel空间的TCP/IP是没有办法做到这一点的(受限于打开文件数量、维护每个连接的开销、操作系统处理网卡的方式),解决办法正是这种——kernel bypass的思路。
已经有人做了相关的工作比如libuinet、dpdk-ans,甚至还有人基于dpdk + dpdk-ans(用户空间TCP/IP协议栈) 移植了nginx(dpdk-nginx)。陈硕老师有一篇《C1000k 新思路:用户态TCP/IP 协议栈》大家也可以学习一下。
【本文是51CTO专栏作者邢森的原创文章,转载请联系作者本人获取授权】