
简介ARP协议是局域网中IP与MAC地址转换的基础机制但因其不验证应答真实性成为中间人攻击的经典入口。理解这一原理不仅能解释为何传统防火墙难以拦截也能为网络管理员提供防御思路。在渗透测试与网络审计场景中通过构造伪造的ARP应答可以将流量导向攻击机实现数据嗅探或会话劫持。本文基于一份十余年前的开源ARP欺骗源码详细分析其基于WinPcap的报文构造、缓存刷新、会话跟踪等核心实现并结合Wireshark抓包与主流防御机制如DAI、静态ARP进行验证。此外还展示了如何将防御思路逆向应用将攻击代码改造为ARP网络审计工具用于监控IP-MAC映射变化和异常广播。这份老旧代码折射出的协议安全本质至今仍有重要的学习价值。1. 为什么一个十几年前的 ARP 欺骗源码今天还值得拆先说结论现在内网里依然能跑起来的 ARP 攻击大半还是这套逻辑——构造伪造的 ARP 应答把网关流量骗到攻击者机器上。这个arp.rar里带的是一个基于 WinPcap 的实现源码只有arppcap.cpp一个主文件配套了 VC6 到 VS2005 之后的完整工程Linux 下也有libpacket.a和libwpcap.a静态库可以直接链。文件列表里值得注意的不是攻击代码本身而是它的组织结构tcp_session.h、normal_lookup.h、bucket_lookup.h这几个头文件说明它内部带了会话跟踪和哈希查找并不是简单把伪造包发出去就完事这对想做协议分析和数据转发的从业者比攻击本身更有参考价值。它标称突破防火墙实际意义要打折扣。传统防火墙过滤的是 IP 和端口而 ARP 工作在数据链路层报文根本没有 IP 层内容可查所以 ARP 欺骗能绕开大部分包过滤型设备。但如果交换网开了 DAIDynamic ARP Inspection或端口安全这种攻击直接失效。这篇文章从协议原理讲到工程复现再到如何用这套代码反向做成监控工具适合做网络协议开发、安全测试以及运维排查的人往下看。2. 先立住 ARP 原理数据转发链路里为什么有空子可钻2.1 ARP 是 L2 和 L3 之间的翻译官但它默认不验证对错以太网帧在局域网里从一个网卡到另一个网卡靠的是 MAC 地址而不是 IP 地址。当上层协议栈要把一个 IP 数据报发出去而本地 ARP 缓存里查不到目标 IP 对应的 MAC 时就发一个广播的 ARP 请求问谁是 192.168.1.1请把你的 MAC 告诉我。被问到的机器回一个单播 ARP 应答请求方把这个映射写进缓存后续发送就不再广播了。问题出在接收方如何处理应答。RFC 826 规定收到 ARP 应答后应当把这个 IP 到 MAC 的映射更新到缓存里但不要求验证这个应答是否真的来自 IP 地址对应的那台设备。于是就有了经典的欺骗路径攻击者持续发送伪造的 ARP 应答把网关 IP 映射到自己的 MAC同时也可以把受害主机 IP 映射到自己的 MAC让双向流量都经过自己。下面这个数据包抓取结果能直观看到欺骗前后的差异## 欺骗前正常网关 08:00:27:aa:bb:cc 广播 ff:ff:ff:ff:ff:ff, ARP, 请求 192.168.1.1 tell 192.168.1.100 08:00:27:11:22:33 08:00:27:aa:bb:cc, ARP, 回复 192.168.1.1 is-at 08:00:27:11:22:33 ## 欺骗后网关 MAC 被替换成攻击者网卡 08:00:27:11:22:33 广播 ff:ff:ff:ff:ff:ff, ARP, 回复 192.168.1.1 is-at 08:00:27:11:22:33上面的对比暴露了两个要点第一正常的 ARP 应答是单播的广播的应答本身就值得警惕第二应答里的发送端 MAC 和以太网帧头里的源 MAC 如果不一致说明帧头被改写过。这个项目的源码里必然有类似的处理逻辑不然就没法完成改源 MAC、改 ARP 头、重新算校验这一串动作。2.2 ARP 缓存刷新机制和欺骗的持续性问题把伪造应答发出去一次并不够。Windows 的 ARP 缓存条目默认存活时间是 2 到 10 分钟具体看系统版本到期后会重新发 ARP 请求。更麻烦的是如果受害者在这个周期内收到了真实的网关 ARP 应答缓存会被纠正回去。所以一个合格的欺骗程序必须周期性地重发伪造包保持缓存一直指向错误 MAC。这个源码包里count_packets.h的存在说明程序统计了发送包数你可以把它理解为心跳包计数每秒钟发送一批伪造 ARP 应答数量越多抢占缓存的概率就越高。我一般在测试环境里设置为每 1 到 2 秒发一次太频繁会触发交换机的 ARP 限速策略反而不利于隐蔽。// 典型的周期性 ARP 应答发送逻辑 while (running) { send_arp_reply(pcap_handle, target_ip, spoof_ip, attacker_mac, target_mac); sleep(1); // 每秒刷新一次受害者缓存 }这段代码对应源码里主循环的常见写法pcap_sendpacket负责把构造好的帧发出去sleep控制刷新频率。参数不复杂但如果把节奏调成每 100 毫秒一次很快就能看到交换机上报%PORT_SECURITY-2-PSECURE_VIOLATION之类的日志这就是频率和检测机制的博弈点。2.3 为什么传统防火墙拦不住但交换机可以包过滤防火墙工作在 L3/L4检查的是 IP 头和 TCP/UDP 端口当一个 ARP 报文以广播形式出现在链路上时防火墙根本不会把它当会话的一部分来处理。这就是 突破防火墙 说法的来源但更准确的说法是绕过了包过滤。真正能拦住它的设备是交换机。DAI 依赖 DHCP Snooping 建立的 IP-MAC-Port 绑定表对经过每个端口的 ARP 报文做校验如果报文中声称的 IP-MAC 映射不在绑定表里直接丢弃。这个项目的局限性就在这里它假设攻击者处于一个没有 DAI 的二层广播域里。在复现的时候要意识到你能跑通不代表这个技术在当前网络环境下依然无解更多的是理解攻击面本身。3. 源码解剖从 arppcap.cpp 到 WinPcap 的完整调用链3.1 工程文件告诉我们的编译时代背景解压后第一眼看.dsw和.dsp这是 VC6 的工程格式arppcap.sln和arppcap.vcproj则说明项目后来被迁移到了 VS2002 之后的环境。WpdPack是 WinPcap 开发包的标准目录名Inc和Lib分开存放头文件与导入库代码里需要配置两步工程配置项值说明C/C 预处理器WPCAP、HAVE_REMOTE必须定义否则 pcap.h 不会启用 Windows 扩展和远程抓包接口附加包含目录指向Inc目录里面是pcap.h、Packet32.h、remote-ext.h附加依赖库wpcap.lib、Packet.lib前者是抓包 API后者是 NPF 驱动接口从normal_lookup.h和bucket_lookup.h这两个文件的名字可以推断项目里对局域网 IP 建立了一张哈希表用来快速查目标主机是否在线、MAC 地址是什么。tcp_session.h则说明它还尝试跟踪 TCP 会话状态这在纯欺骗工具里不算常见更像是为了后面做流量转发或会话劫持预留的模块。3.2 WinPcap 发送 ARP 帧的四个关键 APIarppcap.cpp再长核心也脱不开这四条调用链。第一步是pcap_findalldevs枚举网卡第二步是pcap_open_live打开指定网卡第三步是构造 42 字节的 ARP 报文14 字节以太网头加 28 字节 ARP 头第四步是pcap_sendpacket发送。// 打开网卡并设置为非混杂模式这里用于发送而非捕获 pcap_t *handle; char errbuf[PCAP_ERRBUF_SIZE]; handle pcap_open_live(eth0, 65536, 0, 1000, errbuf); if (handle NULL) { fprintf(stderr, open device failed: %s\n, errbuf); return -1; } // 构造以太网头目标 MAC 使用广播地址或已知目标 MAC struct eth_header { unsigned char dst_mac[6]; // 目的 MAC unsigned char src_mac[6]; // 源 MAC填攻击者自己的地址 unsigned short ether_type; // 0x0806 表示 ARP } __attribute__((__packed__));这段代码说明两个容易被忽略的细节pcap_open_live的第三个参数设为 0意味着不进入混杂模式因为欺骗只要发送不需要接收所有流量但如果你想做双向欺骗既骗网关又骗主机就必须把pcap_loop打开收包否则拿不到目标主机的真实 MAC伪造应答就无处可发。这是新手最容易卡住的地方。3.3 ARP 报文字段和校验的坑ARP 头部结构比以太网头复杂每个字段都有字节序要求。硬件类型HTYPE填 1 表示以太网协议类型PTYPE填 0x0800 表示 IP硬件地址长度 6协议地址长度 4。操作码OPER是关键请求是 1应答是 2。欺骗场景里必须填 2否则目标主机会忽略这条报文。// ARP 头构造注意网络字节序 struct arp_header { unsigned short htype; // 硬件类型1 表示以太网 unsigned short ptype; // 协议类型0x0800 表示 IPv4 unsigned char hlen; // MAC 地址长度 6 unsigned char plen; // IP 地址长度 4 unsigned short oper; // 1 请求2 应答 unsigned char sender_mac[6]; // 发送端 MAC unsigned char sender_ip[4]; // 发送端 IP unsigned char target_mac[6]; // 目标 MAC unsigned char target_ip[4]; // 目标 IP } __attribute__((__packed__)); // 把 IP 字符串转换为字节序 inet_pton(AF_INET, 192.168.1.1, arp.sender_ip);这段代码里最坑的是sender_ip和target_ip的字节序。如果用inet_addr得到的是网络字节序直接拷贝就行但如果你在 Windows 上用了inet_ntoa再转回来就很容易掉进本地字节序和网络字节序互转的陷阱里。项目里的Gnuc.h和pcap-stdinc.h大概率就是为跨平台字节序和基础类型做的适配层。3.4 发完之后为什么还要监听如果我只看下载包里的文件列表会发现arppcap这个名字本身是 ARP 和 pcap 的组合这意味着它不只是发还要收。为了获取目标主机的真实 MAC 地址源码里一定有一段基于pcap_next_ex的循环等待目标主机发出任何一场 IP 包然后从以太网头提取源 MAC。这一步不做欺骗只能对广播域内的所有主机盲目发应答效率低并且容易暴露。// 捕获一个 IP 数据包解析出源 MAC 作为目标主机的真实地址 struct pcap_pkthdr *header; const unsigned char *packet; int result pcap_next_ex(handle, header, packet); if (result 1 packet ! NULL) { unsigned char *dst_mac (unsigned char *)packet; // 以太网帧目标 MAC unsigned char *src_mac (unsigned char *)packet 6; // 源 MAC // 判断上层协议是否为 IP然后拷贝 src_mac 到目标主机 MAC 字段 }这段监听逻辑很重要它决定了欺骗是无差别广播还是精准定向。正常做法是只欺骗你要攻击的目标和网关其他主机的 ARP 缓存不要动动静越小越难被发现。这也是为什么源码里会带session_lookup相关的头文件——它需要维护一张哪些 IP 已经被欺骗、哪些还没被欺骗的状态表避免重复发送同样的应答浪费带宽。3.5 Linux 下的移植路径libpcap 静态库的作用压缩包里同时出现了libpacket.a和libwpcap.a这是 Linux 下编译的产物。WinPcap 的wpcap.lib本质是对 libpcap 的 Windows 移植所以源码可以无缝切换。在 Linux 下编译只需要装上libpcap-dev然后把arppcap.cpp直接编译# 若没有 libpcap 开发库则先安装 sudo apt-get install libpcap-dev # 编译源码静态链接 libpcap gcc -o arppcap arppcap.cpp -lpcap -lpthread这里有个实际的小坑-lpthread要加因为项目里的线程同步头文件semaphore.h、count_packets.h暗示它开了线程来分别处理发包和收包。如果编译时没有链接 pthread运行时会直接段错误排查这种问题最快的方法是 gdb 跑一下bt看线程栈。4. 复现与实战从编译到抓包验证一套完整流程4.1 Windows 下配置和编译的完整步骤拿到源码后我一般先打开arppcap.sln如果用的是 VS2010 以上版本会提示进行格式转换确认即可。然后按三个步骤配置步骤 1项目属性 - C/C - 预处理器 - 预处理器定义添加 WPCAP 和 HAVE_REMOTE 步骤 2项目属性 - C/C - 常规 - 附加包含目录填入 WpdPack 的 Include 路径 步骤 3项目属性 - 链接器 - 常规 - 附加库目录填入 WpdPack 的 Lib 路径编译完成后第一次运行大概率出现找不到 wpcap.dll的报错。把 WinPcap 运行库装好或者把wpcap.dll、packet.dll放到 exe 同目录即可。这个操作虽然是基础项但对初学者来说是最容易卡壳的环节。常见于下载了开发包但没装运行库或者装了 64 位版本而源码是 32 位编译。4.2 指定网卡和 IP 参数的两种方式程序的输入参数通常有两种格式一种是用索引或名称指定网卡另一种是直接命令行传参指定目标 IP 和网关 IP。基于 WPcap 的常见实现参数大概长这样# 运行程序网卡名称为以太网目标是 192.168.1.100网关是 192.168.1.1 arppcap.exe 以太网 192.168.1.100 192.168.1.1如果程序打印了网卡列表而不是直接运行说明它支持交互式选择数字序号对应pcap_findalldevs枚举出的设备。我一般先用dumpcap -D或windump -D查看当前系统网卡的准确名称避免因为网卡名含空格导致参数错位。4.3 用 Wireshark 做抓包验证的完整链路欺骗有没有生效不用看数据包统计直接在受害主机上arp -a看缓存最直观接口: 192.168.1.100 --- 0x0c Internet 地址 物理地址 类型 192.168.1.1 aa-bb-cc-dd-ee-ff 动态如果这个aa-bb-cc-dd-ee-ff对应的是攻击者的 MAC 而不是真实网关 MAC那欺骗就对受害者生效了。接下来看数据流向在受害者上ping 8.8.8.8同时用 Wireshark 在攻击者网卡上抓包能看到受害者的 ICMP 请求先到达攻击者再到真实网关回来时再经过攻击者转回受害者。这就是中间人链路。# 在受害主机上持续 ping 外部地址观察是否丢包或延迟突增 ping -t 8.8.8.8 # 在攻击者主机上检查是否收到目标主机的流量 tcpdump -i eth0 icmp这两条命令配合起来看如果攻击者机器上没抓到 ICMP 包但受害者的 ping 正常说明缓存可能已经被真实网关刷新欺骗断了。这时候回到程序控制台看发包计数器确认是不是停止了周期性发送。4.4 网关欺骗和目标欺骗的双向差异在做网关欺骗时只向受害者发送伪造的网关 ARP 应答就够了网关自己的缓存不一定要动。但要想截获网关回给受害者的流量必须让网关认为受害者的 MAC 是攻击者的这就要对网关也发一条伪造应答。双向欺骗模式下受害者发给网关和网关返回给受害者的包都会经过攻击者的网卡这时必须开启 IP 转发否则两边通信直接断裂。# Linux 下开启 IP 转发 echo 1 /proc/sys/net/ipv4/ip_forward # Windows 下通过注册表开启 reg add HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters /v IPEnableRouter /t REG_DWORD /d 1这一步很多人会忘结果是一开启欺骗整个网络断掉。断网本身也是攻击效果的一种但如果要做中间人监听就必须把转发放开。源码里的tcp_session.h的价值就在这里——它可以跟踪 TCP 双向流的状态确保转发后的会话序列号和确认号不因为多跳而错乱。没有会话跟踪大流量传输会频繁重传。4.5 失败时的排查顺序清单遇到欺骗不生效按下面的顺序排查命中率最高排查项验证命令说明网卡是否选对windump -D或者ipconfig对比无线网卡和有线网卡名称容易混淆攻击者 IP 是否和受害者同网段ipconfig /all查看网段ARP 无法跨网段工作这是前提受害者是否有静态 ARParp -a看类型是否为静态静态表项不会接受广播 ARP 应答交换机是否启用 DAI登录交换机查show ip arp inspectionDAI 会把欺骗包直接丢进黑洞这些验证命令在真实环境里非常关键。排错时你会发现多数失败不是代码问题而是网络设计本身已经有了防御机制这也是这个源码作为教学资源最有价值的部分——它让你亲手验证了各种防御手段的实际效果。5. 检测与防御用同样的协议知识反制 ARP 欺骗5.1 靠 ARP 缓存对比和包频率来识别异常既然欺骗的本质是非预期地修改了 IP-MAC 映射那么检测的思路就是验证映射的正确性和变更频率。最朴素的做法是拿arp -a的输出去对交换机show mac address-table的接口学到的 MAC比对同一个 IP 在两个视图里的 MAC 是否一致。更主动一点可以用脚本持续监听arp -a的变更时间正常情况下周级的变更如果分钟级就告警。# 持续记录 arp 缓存变化输出带时间戳的差异 while true; do arp -a | md5sum /tmp/arp_hash.log date %s /tmp/arp_timestamp.log sleep 10 done这种监控在小型网段够用但在大网段里会造成额外的 ARP 流量。商用环境一般会启用交换机上的 DHCP Snooping 和 DAI。DAI 的校验原理前面已提过它会把每个 ARP 报文的sender_mac、sender_ip与 DHCP 绑定表匹配不匹配即丢弃。要做无 DHCP 环境的 DAI就得手动写arp access-list配置量会大很多。5.2 在主机侧用静态 ARP 阻断欺骗对付单点欺骗给关键主机配置静态 ARP 是效率最高的方式。Windows 下删除错误的动态条目再添加静态条目# 删除错误的动态缓存 arp -d 192.168.1.1 # 添加正确的静态映射需要管理员权限 arp -s 192.168.1.1 aa-bb-cc-11-22-33静态 ARP 的缺点是要到每一台机器上配网关变更或设备更换时必须逐台改不适合大中型网络。它的意义在于防御原理很直观ARP 协议本身不验证应答的真实性静态绑定相当于把验证提前做完了。在做实验验证时这个方法最适合——不用动交换机一条命令就让攻击工具的欺骗失效。5.3 网关侧的欺骗检测和自动封禁思路网关是双向欺骗的第二端网关上的检测比终端更有全局视野。在 Linux 网关上用arpwatch监控 IP-MAC 对应关系的变化一旦出现 MAC 地址在短时间内切换或一个 MAC 对应多个 IP就触发告警。# 安装并启动 arpwatch发送告警到 syslog apt-get install arpwatch arpwatch -i eth0 # 监控日志中是否有 changed 或 flip 关键字 tail -f /var/log/messages | grep -i arpwatch更高级的做法是主动探测来进行防御比如持续向网关发送合法的 ARP 应答周期比攻击者短抢占缓存更新窗口。这种以攻对攻的技术有一定效果但会让局域网内的 ARP 流量显著增加和前面提到的心跳包刷新是同一原理只是立场反了过来。一般不建议在生产环境用实验场景里验证对抗思路倒是不错。5.4 抓包特征一眼识别 ARP 欺骗从 Wireshark 过滤出所有 ARP 报文能看到两个明显的异常特征一是同一时间里同一个 IP 对应多个 MAC 地址二是同一个 MAC 地址声明了多个 IP。过滤器可以这样写arp.duplicate-address-detected这是 Wireshark 自带的异常检测字段能直接标记重复地址的包。另外配合以太网帧头的源 MAC 和 ARP 头里的 sender MAC 做对比不一致就基本可以认定是伪造帧eth.src ! arp.src.proto_hw_mac这条过滤器在实战里非常有用。正常机器发 ARP 请求时以太网头源 MAC 和 ARP 头的 sender_hw 一定是同一个值只有刻意构造的伪造包才会出现不一致。不过在判断时要留一个心眼部分网卡驱动在做 VLAN offload 时可能改写帧头导致误报最好多抓几个包交叉确认。6. 二次开发把欺骗程序改造成 ARP 网络审计工具这个项目的价值不止在攻击侧。保留arppcap.cpp的数据包捕获框架和normal_lookup.h的查找表去掉周期发包逻辑换成被动监听模式就是一个 ARP 网络审计工具。改造点集中在三处入口逻辑、报文解析、告警输出。// 改为被动模式只监听的 ARP 应答 static void packet_handler(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) { struct eth_header *eth (struct eth_header *)packet; if (ntohs(eth-ether_type) ! 0x0806) { return; // 非 ARP 包直接丢弃 } struct arp_header *arp (struct arp_header *)(packet 14); // 在这里更新 IP-MAC 映射表并检查是否有冲突 update_lookup_table(arp-sender_ip, arp-sender_mac); } int main(int argc, char **argv) { pcap_t *desc pcap_open_live(eth0, 65536, 1, 1000, errbuf); pcap_loop(desc, -1, packet_handler, NULL); }这段代码里把pcap_open_live的第三个参数改成了 1打开了混杂模式。解析出 ARP 包的 sender IP 和 sender MAC 后把它放入哈希表下一次再见到同一 IP 时如果 MAC 变了就触发告警。这里的核心设计是用normal_lookup.h的哈希结构维护 IP 到 MAC 的映射每次新包到来做一次比对时间复杂度是 O(1)在几千台设备的局域网里都扛得住。改造后的工具还能顺便统计 ARP 包的速率和广播比例。正常情况下局域网内 ARP 广播不超过总包数的 1%欺骗工具的周期重发会产生明显的连续广播尖峰。在代码里维护两个计数器每秒打印一次速率就能在上下文中看到完整的网络行为画像。如果要继续深挖可以把tcp_session.h的会话表也接进来——当一个 IP 的 MAC 频繁变化的同时这个 IP 却有活跃的 TCP 会话存在基本可以判定是中间人劫持这时把告警写入日志文件即可。整个过程只需要复用源码里现成的数据结构不需要从零写轮子。这个项目最值得学习的地方正在于此同一套协议处理代码站在不同立场上就是完全不同的工具。本文还有配套的精品资源点击获取