ARP (Address Resolution Protocol) 將 IPv4 地址解析為 MAC 地址。在 Linux 下,通過 **原始套接字 (`PF_PACKET`, `SOCK_RAW`)** 可直接發送/接收鏈路層幀,實現 ARP 掃描、代理 ARP、網路攻擊測試等功能。本文提供完整、可編譯的 C 代碼,手動構造以太網頭 + ARP 負載,支援自定義源/目標 IP/MAC。
#include <linux/if_ether.h> // 以太網頭定義
#include <netinet/if_ether.h> // ARP 結構 (部分系統)
#pragma pack(push, 1)
struct ArpPacket {
// 以太網頭 (14 bytes)
uint8_t dst_mac[6]; // 目標 MAC (廣播 ff:ff:ff:ff:ff:ff)
uint8_t src_mac[6]; // 源 MAC
uint16_t eth_type; // 0x0806 = ARP
// ARP 負載 (28 bytes for IPv4/Ethernet)
uint16_t htype; // 硬件類型 1 = Ethernet
uint16_t ptype; // 協議類型 0x0800 = IPv4
uint8_t hlen; // 硬件地址長度 6
uint8_t plen; // 協議地址長度 4
uint16_t oper; // 1=請求 2=應答
uint8_t sha[6]; // 發送方硬件地址
uint8_t spa[4]; // 發送方協議地址
uint8_t tha[6]; // 目標硬件地址 (請求時全 0)
uint8_t tpa[4]; // 目標協議地址
uint8_t pad[18]; // 填充至最小幀長 64B (含 FCS)
};
#pragma pack(pop)
#define ETH_P_ARP 0x0806
#define ARPHRD_ETHER 1
#define ARPOP_REQUEST 1
#define ARPOP_REPLY 2
#define IFACE "eth0" // 預設介面
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <net/if_arp.h>
void die(const char* msg) { perror(msg); exit(1); }
void parse_mac(const char* str, uint8_t* mac) {
sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
}
void parse_ip(const char* str, uint8_t* ip) {
struct in_addr addr;
inet_pton(AF_INET, str, &addr);
memcpy(ip, &addr, 4);
}
int main(int argc, char* argv[]) {
if (argc != 6) {
fprintf(stderr,
"Usage: %s \n"
"Example: %s 192.168.1.100 aa:bb:cc:dd:ee:ff 192.168.1.1 ff:ff:ff:ff:ff:ff 5\n",
argv[0], argv[0]);
return 1;
}
// 1. 建立原始套接字
int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock < 0) die("socket");
// 2. 獲取介面索引
struct ifreq ifr;
strncpy(ifr.ifr_name, IFACE, IFNAMSIZ-1);
if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) die("SIOCGIFINDEX");
int ifindex = ifr.ifr_ifindex;
// 3. 綁定介面
struct sockaddr_ll sll = {0};
sll.sll_family = AF_PACKET;
sll.sll_ifindex = ifindex;
sll.sll_protocol = htons(ETH_P_ARP);
if (bind(sock, (struct sockaddr*)&sll, sizeof(sll)) < 0) die("bind");
// 4. 解析參數
struct ArpPacket pkt = {0};
parse_mac(argv[2], pkt.src_mac);
parse_mac(argv[2], pkt.sha); // 發送方 MAC = 源 MAC
parse_ip(argv[1], pkt.spa); // 發送方 IP
parse_mac(argv[4], pkt.dst_mac); // 目標 MAC (通常廣播)
parse_mac(argv[4], pkt.tha); // 目標 MAC (請求時 0)
parse_ip(argv[3], pkt.tpa); // 目標 IP
// 5. 填充固定字段
pkt.eth_type = htons(ETH_P_ARP);
pkt.htype = htons(ARPHRD_ETHER);
pkt.ptype = htons(ETH_P_IP);
pkt.hlen = 6;
pkt.plen = 4;
pkt.oper = htons(ARPOP_REQUEST); // 發送 ARP 請求
// 6. 發送循環
int count = atoi(argv[5]);
for (int i = 0; i < count; ++i) {
ssize_t sent = sendto(sock, &pkt, sizeof(pkt), 0,
(struct sockaddr*)&sll, sizeof(sll));
if (sent < 0) die("sendto");
printf("Sent ARP request #%d\n", i+1);
usleep(100000); // 100ms 間隔
}
close(sock);
return 0;
}
gcc -std=c99 -Wall -o send_arp send_arp.c
sudo ./send_arp 192.168.1.100 aa:bb:cc:dd:ee:ff 192.168.1.1 ff:ff:ff:ff:ff:ff 3
| 功能 | 實現要點 |
|---|---|
| ARP 掃描整個子網 | 循環 `tpa` 遍歷 1~254,非阻塞 `recvfrom` 收回應 |
| ARP 欺騙/投毒 | 發送偽造 `ARP Reply`,將 `sha/spa` 設為網關 IP + 攻擊者 MAC |
| 代理 ARP | 路由器對非本網段 IP 回應自身 MAC,啟用 `/proc/sys/net/ipv4/conf/all/proxy_arp` |
| 接收解析 | `recvfrom` + 檢查 `eth_type==0x0806` + `oper==2` 解析 `sha/spa` 更新緩存 |
| 跨平台 (Windows) | WinPcap/Npcap `PacketSendPacket` + `ETHER_HEADER` + `ARP_HEADER` |
ARP 協議無認證機制,極易被利用進行中間人攻擊、流量劫持、DoS。**僅在授權網路環境、實驗室、滲透測試範圍內使用**。生產網路部署動態 ARP 檢測 (DAI)、端口安全、靜態 ARP 綁定等緩解措施。