ARP原理概述——基於WinPcap傳送ARP請求資料包獲取遠端MAC地址
ARP協議
ARP概述
網際網路中,IP地址的作用是遮蔽物理網路地址的差異,方便訪問和資料傳輸,為上層使用者提供一種統一的地址格式。但是地層的物理網路還是得通過實體地址傳送和接收資訊。那麼當兩臺主機進行通訊的時候其實還是需要知道MAC地址的。ARP(Address Resolution Protocol)是乙太網中常用的經IP地址與MAC地址進行對映的方法。
ARP工作原理
乙太網的一個特點就是支援廣播而且MAC地址長度是固定的,ARP協議的設計與實現很好的利用了這個特點。在一個乙太網中,主機A想要獲取主機B的MAC地址的話,工作過程如圖所示:
(1)主機A檢查自己的快取記憶體區(圖中的cache表)中的ARP表,看是否能找到B的IP與MAC對映關係,如果能找到就解析結束,找不到進入步驟(2).
(2)主機A廣播一個帶有B的IP地址的請求包,請求B的IP地址與MAC地址對映關係。這個包中還包含A自身的IP地址與MAC地址。
(3)乙太網上所有主機都會收到這個請求包,然後將A的IP地址與MAC地址的對映關係存到各自的快取記憶體區
(4)主機B收到以後回送帶有自身IP地址與MAC地址對映關係的包
(5)A收到主機B的回覆後將B的IP地址與MAC地址對映關係存入自己的快取記憶體區
注意,這個過程中的“主機”也可以是路由器。
ARP資料包格式
編寫程式傳送ARP請求獲取本機和遠端IP的MAC
這個程式的整個過程可以大體分為兩步:
(1)向本機傳送ARP請求獲取本機MAC地址
(2)通過本機的IP與MAC地址,向遠端主機傳送ARP請求,然後獲取對方MAC地址
需要用到的標頭檔案有:
#include <stdio.h>
#include <tchar.h>
#include <stdio.h>
#include <tchar.h>
#include <iostream>
#include <WinSock2.h>
#include <Windows.h>
#include <conio.h>
#ifndef HAVE_REMOTE
#define HAVE_REMOTE
#endif
#include <pcap.h>
#include <remote-ext.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "packet.lib")
#pragma comment(lib, "wpcap.lib")
#define _AFXDLL
using namespace std;
注意,#define HAVE_REMOTE 這一句一定要寫到#include<pcap.h>之前
接下來是用到的一些資料結構和功能函式
//乙太網資料包結構
typedef struct Ethernet_head
{
u_char DestMAC[6]; //目的MAC地址 6位元組
u_char SourMAC[6]; //源MAC地址 6位元組
u_short EthType;
};
//ARP資料包頭結構
typedef struct ARPFrame_t
{
unsigned short HardwareType; //硬體型別
unsigned short ProtocolType; //協議型別
unsigned char HardwareAddLen; //硬體地址長度
unsigned char ProtocolAddLen; //協議地址長度
unsigned short OperationField; //操作欄位
unsigned char SourceMacAdd[6]; //源mac地址
unsigned long SourceIpAdd; //源ip地址
unsigned char DestMacAdd[6]; //目的mac地址
unsigned long DestIpAdd; //目的ip地址
};
//arp包結構
struct ArpPacket {
Ethernet_head ed;
ARPFrame_t ah;
};
//執行緒引數
struct sparam {
pcap_t *adhandle;
char *ip;
unsigned char *mac;
char *netmask;
};
struct gparam {
pcap_t *adhandle;
};
struct sparam sp;//傳送執行緒引數
struct gparam gp;//接收執行緒引數
char *myBroad;//本機廣播地址
unsigned char *m_MAC=new unsigned char[6];//本機MAC地址
char *m_IP;//本機IP地址
char *m_mask;//本機網路掩碼
#define IPTOSBUFFERS 12
void ifprint(pcap_if_t *d);//列印網路卡資訊
char *iptos(u_long in);//主機序到網路序的轉換函式
int GetSelfMac(pcap_t *adhandle, const char *ip_addr, unsigned char *ip_mac);//獲取自身MAC
DWORD WINAPI SendArpPacket(LPVOID lpParameter);//傳送ARP執行緒
DWORD WINAPI GetLivePC(LPVOID lpParameter);//接收回複執行緒
主函式內容如下:
int _tmain(int argc, _TCHAR* argv[])
{
pcap_if_t *alldevs;
pcap_if_t *d;
pcap_addr_t *a;
int num = 0;
char errbuf[PCAP_ERRBUF_SIZE];
struct tm *ltime;
time_t local_tv_sec;
char timestr[16];
struct pcap_pkthdr *header = new pcap_pkthdr;
const u_char *pkt_data = new u_char;
int res;
//獲取本機裝置列表
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
//錯誤處理
fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
}
//顯示介面列表
for (d = alldevs; d != NULL; d = d->next)
{
/* 裝置名(Name) */
printf("%s\n", d->name);
/* 裝置描述(Description) */
if (d->description)
printf("\tDescription: %s\n", d->description);
printf("\n");
}
//選擇裝置
d = alldevs;
cout << "輸入選擇的裝置號:" << endl;
cin >> num;
for (int i = 0; i < num - 1; i++)
d = d->next;
ifprint(d);
//開啟指定的網路介面
pcap_t *adhandle;
if ((adhandle = pcap_open_live(d->name, 65536, PCAP_OPENFLAG_PROMISCUOUS, 1000, errbuf)) == NULL)
{
fprintf(stderr, "\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
pcap_freealldevs(alldevs);
return -1;
}
printf("\nlistening on %s...\n", d->description);
GetSelfMac(adhandle, m_IP, m_MAC);
printf("MyMAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
*m_MAC,
*(m_MAC+1),
*(m_MAC+2),
*(m_MAC+3),
*(m_MAC+4),
*(m_MAC+5));
HANDLE sendthread; //傳送ARP包執行緒
HANDLE recvthread; //接受ARP包執行緒
sp.adhandle = adhandle;
sp.ip = m_IP;
sp.netmask = m_mask;
sp.mac = m_MAC;
gp.adhandle = adhandle;
sendthread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)SendArpPacket,
&sp, 0, NULL);
recvthread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)GetLivePC, &gp,
0, NULL);
pcap_freealldevs(alldevs);
CloseHandle(sendthread);
CloseHandle(recvthread);
while (1);
return 0;
}
列印本機資訊函式
/* 列印所有可用資訊 */
void ifprint(pcap_if_t *d)
{
pcap_addr_t *a;
/* IP addresses */
for (a = d->addresses; a; a = a->next)
{
printf("\tAddress Family: #%d\n", a->addr->sa_family);
switch (a->addr->sa_family)
{
case AF_INET:
printf("\tAddress Family Name: AF_INET\n");
if (a->addr)
{
m_IP = iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr);
printf("\tIP Address: %s\n", m_IP);
}
if (a->netmask)
{
m_mask = iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr);
printf("\tNetmask: %s\n", m_mask);
}
if (a->broadaddr)
{
myBroad = iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr);
printf("\tBroadcast Address: %s\n", myBroad);
}
if (a->dstaddr)
printf("\tDestination Address: %s\n", iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
break;
default:
//printf("\tAddress Family Name: Unknown\n");
break;
}
}
printf("\n");
}
網路序和主機序轉換函式
這部分我是在WinPcap的官方指導文件裡找到的,對具體理解IP地址格式很有幫助
char *iptos(u_long in)
{
static char output[IPTOSBUFFERS][3 * 4 + 3 + 1];
static short which;
u_char *p;
p = (u_char *)∈
which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
sprintf(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
return output[which];
}
獲取自己的MAC地址
#define ETH_ARP 0x0806 //乙太網幀型別表示後面資料的型別,對於ARP請求或應答來說,該欄位的值為x0806
#define ARP_HARDWARE 1 //硬體型別欄位值為表示乙太網地址
#define ETH_IP 0x0800 //協議型別欄位表示要對映的協議地址型別值為x0800表示IP地址
#define ARP_REQUEST 1 //ARP請求
#define ARP_REPLY 2 //ARP應答
#define HOSTNUM 255 //主機數量
// 獲取自己主機的MAC地址
int GetSelfMac(pcap_t *adhandle, const char *ip_addr, unsigned char *ip_mac) {
unsigned char sendbuf[42]; //arp包結構大小
int i = -1;
int res;
Ethernet_head eh; //乙太網幀頭
ARPFrame_t ah; //ARP幀頭
struct pcap_pkthdr * pkt_header;
const u_char * pkt_data;
//將已開闢記憶體空間 eh.dest_mac_add 的首 6個位元組的值設為值 0xff。
memset(eh.DestMAC, 0xff, 6); //目的地址為全為廣播地址
memset(eh.SourMAC, 0x0f, 6);
memset(ah.DestMacAdd, 0x0f, 6);
memset(ah.SourceMacAdd, 0x00, 6);
//htons將一個無符號短整型的主機數值轉換為網路位元組順序
eh.EthType = htons(ETH_ARP);
ah.HardwareType = htons(ARP_HARDWARE);
ah.ProtocolType = htons(ETH_IP);
ah.HardwareAddLen = 6;
ah.ProtocolAddLen = 4;
ah.SourceIpAdd = inet_addr("100.100.100.100"); //隨便設的請求方ip
ah.OperationField = htons(ARP_REQUEST);
ah.DestIpAdd = inet_addr(ip_addr);
memset(sendbuf, 0, sizeof(sendbuf));
memcpy(sendbuf, &eh, sizeof(eh));
memcpy(sendbuf + sizeof(eh), &ah, sizeof(ah));
printf("%s", sendbuf);
if (pcap_sendpacket(adhandle, sendbuf, 42) == 0) {
printf("\nPacketSend succeed\n");
}
else {
printf("PacketSendPacket in getmine Error: %d\n", GetLastError());
return 0;
}
while ((res = pcap_next_ex(adhandle, &pkt_header, &pkt_data)) >= 0) {
if (*(unsigned short *)(pkt_data + 12) == htons(ETH_ARP)
&& *(unsigned short*)(pkt_data + 20) == htons(ARP_REPLY)
&& *(unsigned long*)(pkt_data + 38)
== inet_addr("100.100.100.100")) {
for (i = 0; i < 6; i++) {
ip_mac[i] = *(unsigned char *)(pkt_data + 22 + i);
}
printf("獲取自己主機的MAC地址成功!\n");
break;
}
}
if (i == 6) {
return 1;
}
else {
return 0;
}
}
傳送與監聽執行緒
bool flag;
DWORD WINAPI SendArpPacket(LPVOID lpParameter)
{
sparam *spara = (sparam *)lpParameter;
pcap_t *adhandle = spara->adhandle;
char *ip = spara->ip;
unsigned char *mac = spara->mac;
char *netmask = spara->netmask;
printf("ip_mac:%02x-%02x-%02x-%02x-%02x-%02x\n", mac[0], mac[1], mac[2],
mac[3], mac[4], mac[5]);
printf("自身的IP地址為:%s\n", ip);
printf("地址掩碼NETMASK為:%s\n", netmask);
printf("\n");
unsigned char sendbuf[42]; //arp包結構大小
Ethernet_head eh;
ARPFrame_t ah;
//賦值MAC地址
memset(eh.DestMAC, 0xff, 6); //目的地址為全為廣播地址
memcpy(eh.SourMAC, mac, 6);
memcpy(ah.SourceMacAdd, mac, 6);
memset(ah.DestMacAdd, 0x00, 6);
eh.EthType = htons(ETH_ARP);//幀型別為ARP3
ah.HardwareType = htons(ARP_HARDWARE);
ah.ProtocolType = htons(ETH_IP);
ah.HardwareAddLen = 6;
ah.ProtocolAddLen = 4;
ah.SourceIpAdd = inet_addr(ip); //請求方的IP地址為自身的IP地址
ah.OperationField = htons(ARP_REQUEST);
//向區域網內廣播傳送arp包
unsigned long myip = inet_addr(ip);
unsigned long mynetmask = inet_addr(netmask);
unsigned long hisip = htonl((myip & mynetmask));
//向指定IP主機傳送
char desIP[16];
printf("輸入目標IP:");
scanf("%s", &desIP);
//char* desIP = "192.168.43.55";
ah.DestIpAdd = htonl(inet_addr(desIP));
//構造一個ARP請求
memset(sendbuf, 0, sizeof(sendbuf));
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());
}
flag = TRUE;
return 0;
}
DWORD WINAPI GetLivePC(LPVOID lpParameter) //(pcap_t *adhandle)
{
gparam *gpara = (gparam *)lpParameter;
pcap_t *adhandle = gpara->adhandle;
int res;
unsigned char Mac[6];
struct pcap_pkthdr * pkt_header;
const u_char * pkt_data;
while (true) {
if ((res = pcap_next_ex(adhandle, &pkt_header, &pkt_data)) >= 0) {
if (*(unsigned short *)(pkt_data + 12) == htons(ETH_ARP)) {
ArpPacket *recv = (ArpPacket *)pkt_data;
if (*(unsigned short *)(pkt_data + 20) == htons(ARP_REPLY)) {
printf("-------------------------------------------\n");
printf("IP地址:%d.%d.%d.%d MAC地址:",
recv->ah.SourceIpAdd & 255,
recv->ah.SourceIpAdd >> 8 & 255,
recv->ah.SourceIpAdd >> 16 & 255,
recv->ah.SourceIpAdd >> 24 & 255);
for (int i = 0; i < 6; i++) {
Mac[i] = *(unsigned char *)(pkt_data + 22 + i);
printf("%02x ", Mac[i]);
}
printf("\n");
}
}
}
Sleep(10);
}
return 0;
}
注意:
程式碼邏輯整體應該沒啥問題,但是在實際執行的時候,由於我的接收執行緒是在while迴圈裡不停的接收,所以有可能收到很多相同的來自本機的回覆,也就是在獲取本機MAC地址的時候的回覆資訊。你輸入的目標IP的回覆可能需要一段時間才能顯示出來,甚至有可能有時候你等半天也看不到你想要的那個回覆,如果這樣的話建議關閉程式重新執行多試幾次。
出現這個問題的原因目前不明,其實我自己也覺得很奇怪為什麼會收到那麼多相同的回覆,而且我該發現如果我在接收執行緒的迴圈最後不加上那個Sleep(10),那麼程式執行以後就會在pkt_data那裡發生溢位,貌似是由於瞬間接收到太多包導致寫入溢位。但如果我不迴圈接收的話那麼一般是還沒有收到ARP回覆資訊執行緒就關閉了。
我到最後也沒想到為什麼會這樣,也不知道如何解決,如果評論區有大佬看出來問題了的話可以指點一下!
相關文章
- scapy構造列印ARP資料包
- ARP 地址解析協議協議
- ARP協議 地址解析協議:IP地址轉換為MAC地址協議Mac
- 服務端如何獲取客戶端請求IP地址服務端客戶端
- react-fetch資料傳送請求React
- Linux基礎命令---arpLinux
- 獲取客戶端Mac地址客戶端Mac
- ARP地址解析協議-個人總結協議
- ARP(地址解析協議)和RARP(逆地址解析協議)協議
- jmeter之傳送json資料的post請求JMeterJSON
- wireshark抓包curl傳送http2請求HTTP
- 《計算機網路微課堂》實驗2 MAC地址,IP地址,ARP協議計算機網路Mac協議
- Linux獨享主機繫結IP和MAC地址,防止ARP欺騙LinuxMac
- 使用Python獲取HTTP請求頭資料PythonHTTP
- 前端傳送的請求,是如何請求到後端服務的?前端後端
- Postman傳送Post請求Postman
- java傳送http請求JavaHTTP
- Java傳送Post請求Java
- 傳送GET請求 示例
- ARP協議:網路世界的地址翻譯官協議
- Jmeter —— jmeter利用取樣器中http傳送請求JMeterHTTP
- 前端獲取不到後端新增的請求頭資訊前端後端
- SAP UI5 應用的 OData 後設資料請求的傳送原理分析UI
- SpringMVC中如何傳送GET請求、POST請求、PUT請求、DELETE請求。SpringMVCdelete
- Linux 核心引數 arp_ignore & arp_announce 詳解Linux
- 資料包遠端傳輸的抓包系統scratch
- 爬取LeetCode題目——如何傳送GraphQL Query獲取資料LeetCode
- 如何傳送請求以及AJAX
- python傳送HTTP POST請求PythonHTTP
- 使用Feign傳送HTTP請求HTTP
- arp命令 引數
- C# 透過ARP技術來觀察目標主機資料包C#
- Kali 實現區域網 ARP 欺騙和 ARP 攻擊
- ARP協議介紹與ARP協議的攻擊手法協議
- [系列] Go - 基於 GORM 獲取當前請求所執行的 SQL 資訊GoORMSQL
- Vue 使用 Axios 傳送請求的請求體問題VueiOS
- Postman傳送請求引數是Map格式的請求Postman
- Laravel 檔案上傳和獲取請求引數Laravel