skaiuijing

引言

之前介绍了ARP协议及实现。

那么ARP协议能不能被用于网络攻击呢?当然可以。

现在我们将解析ARP数据包及实现数据包重定向或者是ARP欺骗。

ARP欺骗

ARP 欺骗(又称 ARP 中间人攻击)利用 ARP 协议的无状态特性,通过伪造 ARP 报文,诱导目标主机更新 ARP 缓存,将目标 IP 与攻击者的 MAC 地址错误绑定。目标主机更新缓存后,流量不再通过受害主机,而是到达攻击者设备,进而被读取、篡改或丢弃。

原理

ARP 协议不验证响应的真实性,这就给攻击者留下了空间:

攻击者 C 向主机 A 发送伪造的 ARP 响应:

1.IP 地址 B 的 MAC 是 C 的 MAC。

2.A 更新本地 ARP 表,将 B 的 IP 映射到 C 的 MAC。

3.后续所有发往 B 的数据都会先到 C.

利用ARP欺骗原理,这样就实现了一个数据包的重定向。

对于攻击者C来说,它 可以选择:

1.转发给真正的 B(中间人攻击)

2.丢弃(拒绝服务)

3.修改数据后再转发(数据篡改)

网上大部分都是这么介绍ARP欺骗的,但是,我们可能会有一些问题。

问题

为什么流量会到达攻击者设备?既然APR是ip层获取mac的协议,为什么更改mac后不会转发给目标ip呢?

为什么目标主机不能更新为正确的ARP缓存呢?

让我们看看Linux6.6源码就知道了。

Linux内核

其实原因很简单,网络是分层的,当数据包到达Linux网络时,链路层会根据mac进行判断。

数据包接收流程

1.数据包到达网卡(硬件),由驱动接收,进入内核。

2.进入协议栈的第一步是链路层(MAC层)处理,比如以太网驱动会先检查以太网帧头(MAC地址、类型等)。

3.如果MAC地址不匹配(既不是本机、广播、组播等),数据包会被丢弃。

4.只有通过MAC层检查的数据包,才会被上交到网络层(如IP协议)。

5.网络层(IP层)会进一步检查IP头部(如目的IP、校验和等),如果不符合要求(如目的IP不是本机、校验和错误等),也会丢弃。

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
43
44
45
46
47
48
49
50
51
52
53

__be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev)
{
unsigned short _service_access_point;
const unsigned short *sap;
const struct ethhdr *eth;

skb->dev = dev;
skb_reset_mac_header(skb);

eth = (struct ethhdr *)skb->data;
skb_pull_inline(skb, ETH_HLEN);

//如果MAC地址不匹配(既不是本机、广播、多播),数据包会被丢弃
if (unlikely(!ether_addr_equal_64bits(eth->h_dest,
dev->dev_addr))) {
if (unlikely(is_multicast_ether_addr_64bits(eth->h_dest))) {
if (ether_addr_equal_64bits(eth->h_dest, dev->broadcast))
skb->pkt_type = PACKET_BROADCAST;
else
skb->pkt_type = PACKET_MULTICAST;
} else {
skb->pkt_type = PACKET_OTHERHOST;
}
}

/*
* Some variants of DSA tagging don't have an ethertype field
* at all, so we check here whether one of those tagging
* variants has been configured on the receiving interface,
* and if so, set skb->protocol without looking at the packet.
*/
if (unlikely(netdev_uses_dsa(dev)))
return htons(ETH_P_XDSA);

if (likely(eth_proto_is_802_3(eth->h_proto)))
return eth->h_proto;

/*
* This is a magic hack to spot IPX packets. Older Novell breaks
* the protocol design and runs IPX over 802.3 without an 802.2 LLC
* layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This
* won't work for fault tolerant netware but does for the rest.
*/
sap = skb_header_pointer(skb, 0, sizeof(*sap), &_service_access_point);
if (sap && *sap == 0xFFFF)
return htons(ETH_P_802_3);

/*
* Real 802.2 LLC
*/
return htons(ETH_P_802_2);
}

所以说,当ARP欺骗发生后,真实的数据包流程如下:

1.C主机的ARP欺骗成功,A主机更新ARP缓存

2.A主机需要给B主机发消息,直接使用了缓存中错误的mac

3.B主机收到了A主机发来的数据包,但是此时A主机的目标mac都是被欺骗的,因此B主机检查目标mac后发现,既不是本机,也不是广播多播这些,因此B主机选择丢弃该包

4.A主机的数据包到了邪恶的C主机这里。

既然知道了arp欺骗的原理,现在让我们尝试使用eBPF技术解析ARP协议。

eBPF解析ARP协议

ARP的字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ARP 协议头部结构,对应 RFC 826 中的 ARP 报文格式
struct arp_hdr {
unsigned short ar_hrd; // 硬件类型(Hardware Type),如 Ethernet 为 1
unsigned short ar_pro; // 协议类型(Protocol Type),如 IPv4 为 0x0800
unsigned char ar_hln; // 硬件地址长度(Hardware Address Length),Ethernet 为 6
unsigned char ar_pln; // 协议地址长度(Protocol Address Length),IPv4 为 4
unsigned short ar_op; // 操作码(Opcode),1 表示请求,2 表示响应
} __attribute__((packed)); // 禁止结构体自动对齐,确保与网络字节流格式一致

// ARP 报文的以太网封装结构,包含请求方和目标方的地址信息
struct arp_ether {
struct arp_hdr ea_hdr; // ARP 报文头部
unsigned char arp_sha[6]; // Sender Hardware Address:发送方 MAC 地址
unsigned int arp_spa; // Sender Protocol Address:发送方 IP 地址
unsigned char arp_tha[6]; // Target Hardware Address:目标方 MAC 地址(请求时为 0)
unsigned int arp_tpa; // Target Protocol Address:目标方 IP 地址
} __attribute__((packed)); // 同样禁止结构体对齐,确保与实际报文格式匹配

我们选择使用XDP作为挂载点:

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
SEC("xdp")
int capture_arp(struct xdp_md *ctx) {
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end || eth->h_proto != bpf_htons(ETH_P_ARP))
return XDP_PASS;
//跳过网卡头部
struct arphdr *arp = (void *)(eth + 1);
if ((void *)(arp + 1) > data_end ||
arp->ar_hrd != bpf_htons(ARPHRD_ETHER) ||
arp->ar_pro != bpf_htons(ETH_P_IP))
return XDP_PASS;
//跳过ARP头部
unsigned char *arp_data = (unsigned char *)(arp + 1);
if (arp_data + 2 * ETH_ALEN + 2 * sizeof(__be32) > (unsigned char *)data_end)
return XDP_PASS;
//解析ip
unsigned char *src_mac = arp_data;
__be32 src_ip = *(__be32 *)(arp_data + ETH_ALEN);
unsigned char *dst_mac = arp_data + ETH_ALEN + sizeof(__be32);
__be32 dst_ip = *(__be32 *)(arp_data + ETH_ALEN + sizeof(__be32) + ETH_ALEN);
__be16 opcode = arp->ar_op;

return XDP_PASS;
}

如果我们需要构造ARP欺骗,其实可以插入一段代码:

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
// 提取目标 IP(被欺骗者)和源 IP(我们要伪装的 IP)
__be32 target_ip = *(__be32 *)(arp_data + ETH_ALEN + sizeof(__be32) + ETH_ALEN);
__be32 spoof_ip = bpf_htonl(SPOOFED_IP); // 伪装的 IP 地址(例如网关)

// 判断是否需要欺骗(例如目标 IP 是我们想劫持的)
if (target_ip == spoof_ip) {
// 伪代码:构造 ARP 响应包
// struct ethhdr new_eth;
// struct arphdr new_arp;
// unsigned char payload[ARP_PAYLOAD_SIZE];

// new_eth.h_proto = bpf_htons(ETH_P_ARP);
// new_eth.h_dest = 原始请求者的 MAC 地址
// new_eth.h_source = 欺骗者的 MAC 地址(我们)

// new_arp.ar_op = bpf_htons(ARPOP_REPLY);
// new_arp.ar_pro = bpf_htons(ETH_P_IP);
// new_arp.ar_hrd = bpf_htons(ARPHRD_ETHER);
// new_arp.ar_pln = 4;
// new_arp.ar_hln = 6;

// payload = [欺骗者 MAC][spoof_ip][目标 MAC][target_ip]

// 发送伪造包(XDP 无法主动发送)
// 需要配合 TC 或 AF_XDP 用户态程序完成发送,我们可以在传递事件到应用层,然后应用层触发事件发送ARP欺骗
}

结语

本节介绍了ARP协议及ARP欺骗,就先写到这里吧。

下一节要讲什么来着?

想起来了,本来应该先从Linux内存管理算法和ringbuf讲起的,继续回归到数据包与内存吧。