了解掌握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 地址。