了解掌握ARP包结构,编写程序封装并发送ARP包。
1. 以命令行形式运行:
SendArp source_ip source_mac dest_ip dest_mac
其中SendArp为程序名,source_ip为源IP地址,source_mac为源MAC地址,dest_ip为目的IP地址,dest_mac为目的MAC地址。
2. 输出内容:ARP帧的各个字段。
算法流程图:
ARP(Address Resolution Protocol)地址解析协议,用于将网络层的IP地址解析为链路层的MAC地址。ARP包的结构主要包括以下字段:
(在这个结构中,硬件类型、协议类型、硬件地址长度和协议地址长度字段都是固定长度的,而发送者硬件地址和目标硬件地址的长度取决于硬件类型,发送者协议地址和目标协议地址的长度取决于协议类型。)
硬件类型(Hardware type):表示ARP使用的硬件地址类型,例如以太网地址类型为1。协议类型(Protocol type):表示要映射的协议地址类型,例如IPv4地址类型为0x0800。硬件地址长度(Hardware address length):硬件地址的字节长度,例如以太网MAC地址长度为6。协议地址长度(Protocol address length):协议地址的字节长度,例如IPv4地址长度为4。操作码(Opcode):表示ARP操作的类型,例如1表示ARP请求,2表示ARP应答。发送者硬件地址(Sender hardware address):发送方硬件地址,例如以太网MAC地址。发送者协议地址(Sender protocol address):发送方协议地址,例如IPv4地址。目标硬件地址(Target hardware address):目标硬件地址,用于ARP应答包。目标协议地址(Target protocol address):要解析的协议地址,例如IPv4地址。
ARP帧是网络层协议数据单元,它被封装在链路层帧(如以太网帧)中进行传输。
看自己电脑的接口命令
ifconfig
源 MAC 地址
从 ifconfig 输出的 ether 字段获取,例如:
ether ac:c9:06:07:24:76
你的接口 MAC 地址是 ac:c9:06:07:24:76,通常是你的设备的物理地址。
源 IP 地址
从 ifconfig 输出的 inet 字段获取,例如:
inet 192.168.3.108 netmask 0xffffff00 broadcast 192.168.3.255
你的接口 IPv4 地址是 192.168.3.108。
目标 IP 地址
目标 IP 地址是你想向其发送 ARP 请求的设备的 IP 地址,可以通过以下方法获取:
网络设备的 IP 地址: 通常是子网中的另一台设备的 IP 地址。网关 IP 地址: 如果你想和默认网关通信,可以在路由表中找到它:
netstat -rn | grep default
输出中 default 后的 IP 地址通常是网关地址。例如:192.168.3.3。
目标 MAC 地址
目标 MAC 地址有两种情况:
如果你知道目标设备的 MAC 地址,可以直接填写,例如:
dest_mac = "aa:bb:cc:dd:ee:ff"
如果目标设备的 MAC 地址未知(例如广播请求),使用广播地址:
dest_mac = "ff:ff:ff:ff:ff:ff"
下面是一个用Python语言和scapy库编写的示例程序
该程序可以封装并发送ARP包。(可以用pip install scapy进行安装安装scapy库。)
from scapy.all import *
import sys
def send_arp_packet(src_ip, src_mac, dst_ip, dst_mac):
# 创建ARP包
arp = ARP(op=1, pdst=dst_ip, hwdst=dst_mac, psrc=src_ip, hwsrc=src_mac)
# 创建以太网帧
ether = Ether(dst=dst_mac, src=src_mac, type=0x806)
# 组合以太网帧和ARP包
packet = ether/arp
# 打印ARP帧的各个字段
print(f"Hardware type: {arp.hwtype}")
print(f"Protocol type: {arp.ptype}")
print(f"Hardware address length: {arp.hlen}")
print(f"Protocol address length: {arp.plen}")
print(f"Opcode: {arp.op}")
print(f"Sender MAC address: {arp.hwsrc}")
print(f"Sender IP address: {arp.psrc}")
print(f"Target MAC address: {arp.hwdst}")
print(f"Target IP address: {arp.pdst}")
# 发送ARP包
sendp(packet)
if __name__ == "__main__":
if len(sys.argv) != 6:
print("Usage: SendArp source_ip source_mac dest_ip dest_mac")
sys.exit(1)
send_arp_packet(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
/////
from scapy.all import Ether, ARP, sendp, sniff, conf
import psutil
import socket
import sys
# 使用 psutil 和 socket 优化获取网卡列表
def list_network_interfaces():
print("获取可用网卡列表...")
interfaces = psutil.net_if_addrs()
print("以下是系统中可用的网络接口:")
for iface, addrs in interfaces.items():
print(f"接口名称: {iface}")
for addr in addrs:
if addr.family == socket.AF_INET:
print(f" IP 地址: {addr.address}")
elif addr.family == psutil.AF_LINK:
print(f" MAC 地址: {addr.address}")
return interfaces
# 分配并初始化发送包结构
def allocate_and_initialize_packet(source_ip, source_mac, target_ip, target_mac, broadcast=True):
print("分配并初始化发送包结构...")
# 构建 Ethernet 头部
eth = Ether(src=source_mac, dst="ff:ff:ff:ff:ff:ff" if broadcast else target_mac)
# 构建 ARP 帧
arp = ARP(
hwsrc=source_mac,
psrc=source_ip,
hwdst=target_mac if not broadcast else "00:00:00:00:00:00",
pdst=target_ip,
op=1 if broadcast else 2, # ARP 请求为 1,响应为 2
)
packet = eth / arp # 组合 Ethernet 和 ARP
print("发送包已初始化完成")
return packet
# 检测 ARP 响应
def detect_arp_response(interface, target_ip, source_mac):
print(f"开始监听接口 {interface},检测目标 {target_ip} 的 ARP 响应...")
try:
# 定义过滤函数
def arp_response_filter(pkt):
return ARP in pkt and pkt[ARP].psrc == target_ip and pkt[ARP].hwsrc != source_mac
# 使用 sniff 监听
response = sniff(iface=interface, filter="arp", timeout=5, prn=arp_response_filter, count=1)
if response:
print(f"检测到目标 {target_ip} 的 ARP 响应:")
print(f"源 MAC 地址:{response[0][ARP].hwsrc}")
return True
else:
print(f"未检测到目标 {target_ip} 的 ARP 响应")
return False
except Exception as e:
print(f"监听失败:{e}")
return False
# 释放包结构
def release_packet(packet):
print("释放包结构...")
del packet
print("包结构已释放")
# 发送 ARP 数据包
def send_arp_packet_windows(interface, source_ip, source_mac, target_ip, target_mac, broadcast=True):
print("进入发送 ARP 包函数...")
# 分配并初始化发送包
packet = allocate_and_initialize_packet(source_ip, source_mac, target_ip, target_mac, broadcast)
try:
# 发送数据包
print(f"发送 ARP 数据包到 {target_ip}(广播模式: {'是' if broadcast else '否'})...")
sendp(packet, iface=interface, verbose=True)
print("ARP 数据包发送完成")
# 检测 ARP 响应
if detect_arp_response(interface, target_ip, source_mac):
print("ARP 包发送成功,并收到响应")
else:
print("ARP 包发送失败或未收到响应")
except PermissionError:
print("需要管理员权限运行此脚本")
except Exception as e:
print(f"发送失败: {e}")
finally:
# 释放包结构
release_packet(packet)
# 关闭网卡接口
close_interface(interface)
# 关闭网卡接口(仅适用于逻辑层,物理网卡关闭需要其他工具)
def close_interface(interface):
print(f"关闭接口 {interface}...")
# 假关闭,实际操作可能需要管理工具
print(f"接口 {interface} 已关闭(逻辑层)")
将以上代码保存为`SendArp.py`,然后在命令行中运行以下命令(确保有适当的权限):
python SendArp.py source_ip source_mac dest_ip dest_mac
例如:
python SendArp.py 192.168.1.10 00:11:22:33:44:55 192.168.1.20 00:AA:BB:CC:DD:EE
此程序将创建一个ARP请求,将其发送到指定的目标,并打印出ARP帧的各个字段。请注意,发送ARP包可能会对网络造成干扰,请在授权的情况下使用。
需要记清楚的
——ARP(Address Resolution Protocol)帧实际上不是独立的数据帧,而是嵌入在以太网帧的数据部分。以下是以太网帧中包含ARP请求或应答的结构:以太网帧结构:
+---------------------------+ | 目的MAC地址 (6字节) | +---------------------------+ | 源MAC地址 (6字节) | +---------------------------+ | 类型 (2字节) | | 对于ARP来说是0x0806 | +---------------------------+ | 数据 (46-1500字节) | | 对于ARP来说是ARP报文 | +---------------------------+ | 帧校验序列 (4字节) | +---------------------------+
ARP报文结构(嵌入在以太网帧的数据部分):
+---------------------------+ | 硬件类型 (2字节) | | 表示网络接口类型,例如 | | 以太网是0x0001 | +---------------------------+ | 协议类型 (2字节) | | 表示上层协议类型,例如 | | IPv4是0x0800 | +---------------------------+ | 硬件地址长度 (1字节) | | 以太网MAC地址长度是6 | +---------------------------+ | 协议地址长度 (1字节) | | 对于IPv4地址长度是4 | +---------------------------+ | 操作码 (2字节) | | 1表示ARP请求,2表示ARP应答| +---------------------------+ | 发送者硬件地址 (n字节) | | 发送方MAC地址,n是硬件地址| | 长度 | +---------------------------+ | 发送者协议地址 (m字节) | | 发送方IP地址,m是协议地址 | | 长度 | +---------------------------+ | 目标硬件地址 (n字节) | | 目标MAC地址,对于ARP请求 | | 通常为全0 | +---------------------------+ | 目标协议地址 (m字节) | | 目标IP地址 | +---------------------------+
也可以使用C语言完成,不过需要稍微配置一下环境
这段是最重要的:封装ARP包
//14字节以太网首部
struct EthernetHeader
{
u_char DestMAC[6]; //目的MAC地址 6字节
u_char SourMAC[6]; //源MAC地址 6字节
u_short EthType; //上一层协议类型,如0x0800代表上一层是IP协议,0x0806为arp 2字节
};
//28字节ARP帧结构
struct ArpHeader
{
unsigned short hdType; //硬件类型
unsigned short proType; //协议类型
unsigned char hdSize; //硬件地址长度
unsigned char proSize; //协议地址长度
unsigned short op; //操作类型,ARP请求(1),ARP应答(2),RARP请求(3),RARP应答(4)。
u_char smac[6]; //源MAC地址
u_char sip[4]; //源IP地址
u_char dmac[6]; //目的MAC地址
u_char dip[4]; //目的IP地址
};
//定义整个arp报文包,总长度42字节
struct ArpPacket {
EthernetHeader ed;
ArpHeader ah;
};
#include "stdafx.h"
#include
#define ETH_ARP 0x0806 //以太网帧类型表示后面数据的类型,对于ARP请求或应答来说,该字段的值为x0806
#define ARP_HARDWARE 1 //硬件类型字段值为表示以太网地址
#define ETH_IP 0x0800 //协议类型字段表示要映射的协议地址类型值为x0800表示IP地址
#define ARP_REQUEST 1 //ARP请求
#define ARP_RESPONSE 2 //ARP应答
//14字节以太网首部
struct EthernetHeader
{
u_char DestMAC[6]; //目的MAC地址 6字节
u_char SourMAC[6]; //源MAC地址 6字节
u_short EthType; //上一层协议类型,如0x0800代表上一层是IP协议,0x0806为arp 2字节
};
//28字节ARP帧结构
struct ArpHeader
{
unsigned short hdType; //硬件类型
unsigned short proType; //协议类型
unsigned char hdSize; //硬件地址长度
unsigned char proSize; //协议地址长度
unsigned short op; //操作类型,ARP请求(1),ARP应答(2),RARP请求(3),RARP应答(4)。
u_char smac[6]; //源MAC地址
u_char sip[4]; //源IP地址
u_char dmac[6]; //目的MAC地址
u_char dip[4]; //目的IP地址
};
//定义整个arp报文包,总长度42字节
struct ArpPacket {
EthernetHeader ed;
ArpHeader ah;
};
int main()
{
pcap_if_t *alldevs; //所有网络适配器
pcap_if_t *d; //选中的网络适配器
int inum; //选择网络适配器
int i = 0; //for循环变量
pcap_t *adhandle; //打开网络适配器,捕捉实例,是pcap_open返回的对象
char errbuf[PCAP_ERRBUF_SIZE]; //错误缓冲区,大小为256
/* 获取本机设备列表 */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* 打印列表 */
for (d = alldevs; d; d = d->next)
{
printf("%d. %s", ++i, d->name);
if (d->description)
printf(" (%s)\n", d->description);
else
printf(" (No description available)\n");
}
if (i == 0)
{
printf("\nNo interfaces found! Make sure WinPcap is installed.\n");
return -1;
}
printf("Enter the interface number (1-%d):", i);
scanf("%d", &inum);
if (inum < 1 || inum > i)
{
printf("\nInterface number out of range.\n");
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}
/* 跳转到选中的适配器 */
for (d = alldevs, i = 0; i< inum - 1; d = d->next, i++);
/* 打开设备 */
if ((adhandle = pcap_open(d->name, // 设备名
65536, // 65535保证能捕获到不同数据链路层上的每个数据包的全部内容
PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式
1000, // 读取超时时间
NULL, // 远程机器验证
errbuf // 错误缓冲池
)) == NULL)
{
fprintf(stderr, "\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}
/*以上代码在WinPcap开发文档中都可以找到,填充ARP包的代码则要自己编写*/
//开始填充ARP包,填充数据写死在代码中,测试用时数据可随意填写
unsigned char sendbuf[42]; //arp包结构大小,42个字节
unsigned char mac[6] = { 0x00,0x11,0x22,0x33,0x44,0x55 };
unsigned char ip[4] = { 0x01,0x02,0x03,0x04 };
EthernetHeader eh;
ArpHeader ah;
//赋值MAC地址
memset(eh.DestMAC, 0xff, 6); //以太网首部目的MAC地址,全为广播地址
memcpy(eh.SourMAC, mac, 6); //以太网首部源MAC地址
memcpy(ah.smac, mac, 6); //ARP字段源MAC地址
memset(ah.dmac, 0xff, 6); //ARP字段目的MAC地址
memcpy(ah.sip, ip, 4); //ARP字段源IP地址
memset(ah.dip, 0x05, 4); //ARP字段目的IP地址
eh.EthType = htons(ETH_ARP); //htons:将主机的无符号短整形数转换成网络字节顺序
ah.hdType = htons(ARP_HARDWARE);
ah.proType = htons(ETH_IP);
ah.hdSize = 6;
ah.proSize = 4;
ah.op = htons(ARP_REQUEST);
//构造一个ARP请求
memset(sendbuf, 0, sizeof(sendbuf)); //ARP清零
memcpy(sendbuf, &eh, sizeof(eh));
memcpy(sendbuf + sizeof(eh), &ah, sizeof(ah));
//如果发送成功
if (pcap_sendpacket(adhandle, sendbuf, 42) == 0) {
printf("\nPacketSend succeed\n");
}
else {
printf("PacketSendPacket in getmine Error: %d\n", GetLastError());
}
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return 0;
}
参考文章(WinPcap实战(一)——发送ARP包_如何发送arp包-CSDN博客)
注意在Mac 上由于权限问题以及部分功能不同的原因
(在macOS的环境中,socket.AF_PACKET,socket模块没有这个属性。这个属性通常发生在非Unix-like系统上,例如Windows,或者在Unix-like系统上以某种方式配置了Python。在macOS中应该使用socket.AF_INET 和 socket.SOCK_DGRAM 来创建用于发送ARP请求的套接字。)
(注意这是一段python)
import socket
import struct
import binascii
# 定义以太网和ARP常量
ETH_P_ARP = 0x0806
ETH_P_IP = 0x0800
ARPHRD_ETHER = 1
ARPOP_REQUEST = 1
# 定义以太网首部和ARP首部的结构
class EthernetHeader:
def __init__(self, destmac, srcmac, ethertype):
self.destmac = binascii.unhexlify(destmac.replace(':', ''))
self.srcmac = binascii.unhexlify(srcmac.replace(':', ''))
self.ethertype = ethertype
def pack(self):
return struct.pack('!6s6sH', self.destmac, self.srcmac, self.ethertype)
class ArpPacket:
def __init__(self, hrd, pro, hln, pln, op, sha, spa, tha, tpa):
self.hrd = hrd
self.pro = pro
self.hln = hln
self.pln = pln
self.op = op
self.sha = binascii.unhexlify(sha.replace(':', ''))
self.spa = socket.inet_aton(spa)
self.tha = binascii.unhexlify(tha.replace(':', ''))
self.tpa = socket.inet_aton(tpa)
def pack(self):
return struct.pack('!HHBBH6s4s6s4s', self.hrd, self.pro, self.hln, self.pln, self.op,
self.sha, self.spa, self.tha, self.tpa)
def send_arp_packet(interface, source_ip, source_mac, dest_ip, dest_mac):
# 创建原始套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.bind((interface, 0))
# 构建以太网首部
eth_header = EthernetHeader(dest_mac, source_mac, ETH_P_ARP).pack()
# 构建ARP包
arp_packet = ArpPacket(ARPHRD_ETHER, ETH_P_IP, 6, 4, ARPOP_REQUEST,
source_mac, source_ip, dest_mac, dest_ip).pack()
# 发送ARP请求
packet = eth_header + arp_packet
s.send(packet)
# 关闭套接字
s.close()
# 使用示例
if __name__ == "__main__":
import sys
if len(sys.argv) != 5:
print("Usage: python send_arp.py
sys.exit(1)
interface, source_ip, source_mac, dest_ip, dest_mac = sys.argv[1:]
send_arp_packet(interface, source_ip, source_mac, dest_ip, dest_mac)
然后在终端中使用这个命令:
sudo python3 send_arp.py en0 192.168.1.10 00:11:22:33:44:55 192.168.1.1 00:AA:BB:CC:DD:EE
也可以试试这个,不过记得换成自己的接口和IP 地址, IP 地址嗷
from scapy.all import Ether, ARP, sendp
# 定义参数
source_mac = "ac:c9:06:07:24:72" #源 MAC 地址
source_ip = "192.168.3.104"# 源 IP
dest_mac = "ff:ff:ff:ff:ff:ff" # 广播目标 MAC 地址
dest_ip = "192.168.3.1" # 默认网关地址(目标 IP)
# 创建 ARP 请求包
arp_request = ARP(
hwsrc=source_mac,
psrc=source_ip,
hwdst=dest_mac,
pdst=dest_ip,
op=1 # ARP 请求
)
# 创建以太网帧
ethernet_frame = Ether(src=source_mac, dst=dest_mac)
# 组合以太网帧和 ARP 请求
packet = ethernet_frame / arp_request
# 发送数据包
sendp(packet, iface="en0") # 自己的网络接口
print("ARP packet sent successfully.")
最后结果:
如何验证配置:
检查网络连接: 使用 ping 测试目标 IP 是否可达。
ping 192.168.3.1
查看目标设备的 MAC 地址: 如果目标 IP 地址响应了 ping 请求,你可以通过 arp 查看其 MAC 地址
arp -a
输出中会列出目标 IP 和对应的 MAC 地址。