NPCAP 庫是一種用於在Windows
平臺上進行網路封包捕獲和分析的庫。它是WinPcap
庫的一個分支,由Nmap
開發團隊開發,並在Nmap
軟體中使用。與WinPcap
一樣,NPCAP庫提供了一些API
,使開發人員可以輕鬆地在其應用程式中捕獲和處理網路封包。NPCAP庫可以通過WinPcap API
進行程式設計,因此現有的WinPcap應用程式可以輕鬆地遷移到NPCAP庫上。
與WinPcap相比,NPCAP庫具有更好的效能和可靠性,支援最新的作業系統和硬體。它還提供了對802.11
無線網路的本機支援,並可以通過Wireshark
等網路分析工具進行使用。 NPCAP庫是在MIT
許可證下發布的,因此可以在免費和商業軟體中使用。
該工具包分為兩部分組成驅動程式及SDK工具包,在使用本庫進行抓包時需要讀者自行安裝對應版本的驅動程式,此處讀者使用的版本是npcap-1.55.exe
當下載後讀者可自行點選下一步即可,當安裝完成後即可看到如下圖所示的提示資訊;
當驅動程式安裝完成後,讀者就可以自行設定開發工具包到專案中,通常只需要將工具包內的include
及lib
庫設定到專案中即可,如下圖所示設定後自行應用儲存即可。
接著我們來實現第一個功能,列舉當前主機中可以使用的網路卡資訊,該功能的實現主要依賴於pcap_findalldevs_ex()
函數,該函數用於獲取當前系統中可用的所有網路介面卡的列表。
函數的原型宣告如下:
int pcap_findalldevs_ex(const char *source, struct pcap_rmtauth *auth,
pcap_if_t **alldevsp, char *errbuf);
其中,引數含義如下:
pcap_rmtauth
結構來指定遠端的IP和使用者名稱。該函數允許開發者通過一個結構來檢索所有網路介面卡的詳細資訊。它允許指定一個過濾器,以匹配使用者定義的網路介面卡和屬性。此外,pcap_findalldevs_ex()
還提供用於儲存錯誤資訊的結構體,以便在函數呼叫失敗時提供錯誤資訊。
該函數返回值-1表示失敗;否則,返回值為0表示操作成功,並將返回所有可用的網路介面卡和它們的詳細資訊。這些詳細資訊包括介面卡的名稱、描述、MAC地址、IP地址和子網掩碼等,當讀者使用列舉函數結束後需要自行呼叫pcap_freealldevs
函數釋放這個指標以避免記憶體漏失。
以下是pcap_freealldevs函數原型宣告:
void pcap_freealldevs(pcap_if_t *alldevs);
其中,alldevs
引數是指向pcap_if_t
型別結構體的指標,該型別結構體記錄了當前主機上所有可用的網路介面的詳細資訊。pcap_freealldevs()
會釋放傳入的pcap_if_t
型連結串列,並將所有元素刪除。
呼叫pcap_freealldevs()
函數時需要傳入之前通過pcap_findalldevs()
或pcap_findalldevs_ex()
函數獲取到的的指向連結串列結構的指標作為引數。
當有了這兩個函數作為條件,那麼實現列舉網路卡則變得很簡單了,如下程式碼所示則是使用該工具包實現列舉的具體實現流程,讀者可自行編譯測試。
#include <iostream>
#include <winsock2.h>
#include <Windows.h>
#include <string>
#include <pcap.h>
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib, "packet.lib")
#pragma comment(lib, "wpcap.lib")
using namespace std;
// 輸出線條
void PrintLine(int x)
{
for (size_t i = 0; i < x; i++)
{
printf("-");
}
printf("\n");
}
// 列舉當前網路卡
int enumAdapters()
{
pcap_if_t *allAdapters; // 所有網路卡裝置儲存
pcap_if_t *ptr; // 用於遍歷的指標
int index = 0;
char errbuf[PCAP_ERRBUF_SIZE];
/* 獲取本地機器裝置列表 */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &allAdapters, errbuf) != -1)
{
PrintLine(100);
printf("索引 \t 網路卡名 \n");
PrintLine(100);
/* 列印網路卡資訊列表 */
for (ptr = allAdapters; ptr != NULL; ptr = ptr->next)
{
++index;
if (ptr->description)
{
printf("[ %d ] \t [ %s ] \n", index - 1, ptr->description);
}
}
}
/* 不再需要裝置列表了,釋放它 */
pcap_freealldevs(allAdapters);
return index;
}
int main(int argc, char* argv[])
{
enumAdapters();
system("pause");
return 0;
}
編譯並以管理員身份執行程式,則讀者可看到如下圖所示輸出結果,其中第一列為網路卡索引編號,第二列為網路卡名稱;
當有了網路卡編號後則讀者就可以對特定編號進行抓包解析了,抓包功能的實現依賴於pcap_open()
函數,該函數用於開啟一個指定網路介面卡並開始捕獲網路封包,函數的原型宣告如下所示:
pcap_t *pcap_open(const char *source, int snaplen, int flags, int read_timeout,
struct pcap_rmtauth *auth, char *errbuf);
其引數含義如下:
pcap_open_live()
中獲取的名稱。promiscuous
控制器模式或非promiscuous
模式下捕獲。pcap_rmtauth
結構,指定遠端的IP和使用者名稱。該函數返回一個指向pcap_t
型別的指標,該型別結構提供了與網路介面卡通訊的介面,可以用於捕獲封包、關閉網路介面卡及其他操作,讀者在呼叫pcap_open()
函數時,需要指定要開啟的網路介面卡的名稱source
,如果需要設定為混雜模式的話,需要設定flags
引數為PCAP_OPENFLAG_PROMISCUOUS
,此外snaplen
引數用於設定捕獲封包的大小,read_timeout
引數用於設定阻塞讀函數的超時時間,auth
引數則用於指定遠端的IP
和使用者名稱,errbuf
引數用於儲存錯誤資訊。如果該函數返回空,則表示未成功開啟指定的網路介面卡。
另一個需要注意的函數是pcap_next_ex()
該函數用於從開啟的指定網路介面卡中讀取下一個網路封包,通常情況下此函數需要配合pcap_open()
一起使用,其原型宣告:
int pcap_next_ex(pcap_t *p, struct pcap_pkthdr **pkt_header, const u_char **pkt_data);
引數含義如下:
pcap_t
型別結構體的指標,代表開啟的網路介面卡。pcap_pkthdr
型別的指標,該型別結構體包含有關當前封包的後設資料,例如時間戳、封包長度、捕獲到封包的網路介面卡介面等。它返回以下三種返回值之一:
pkt_header
和pkt_data
則指向相關資訊;errbuf
引數中查詢錯誤資訊。使用pcap_next_ex()
函數時,需要提供一個指向pcap_t
型別結構體的指標p
用於確定要從哪個網路介面卡讀取封包。如果讀取封包時成功,則將包的後設資料儲存在傳遞的pcap_pkthdr
指標中,將指向捕獲封包的指標儲存在pkt_data
指標中。如果在指定的時間內未捕獲到任何封包,則函數返回0。如果在讀取封包時發生任何錯誤,則函數返回-1,並在errbuf
引數中提供有關錯誤的詳細資訊。
當讀者理解了上述兩個關鍵函數的作用則就可以實現動態抓包功能,如下程式碼中的MonitorAdapter
函數則是抓包的實現,該函數需要傳入兩個引數,引數1是需要抓包的網路卡序列號,此處我們就使用7號,第二個參數列示需要解碼的封包型別,此處我們可以傳入ether
等用於解包,當然該函數還沒有實現封包的解析功能,這些功能的實現需要繼續完善。
#include <iostream>
#include <winsock2.h>
#include <Windows.h>
#include <string>
#include <pcap.h>
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib, "packet.lib")
#pragma comment(lib, "wpcap.lib")
using namespace std;
// 選擇網路卡並根據不同引數解析封包
void MonitorAdapter(int nChoose, char *Type)
{
pcap_if_t *adapters;
char errbuf[PCAP_ERRBUF_SIZE];
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &adapters, errbuf) != -1)
{
// 找到指定的網路卡
for (int x = 0; x < nChoose - 1; ++x)
adapters = adapters->next;
// PCAP_OPENFLAG_PROMISCUOUS = 網路卡設定為混雜模式
// 1000 => 1000毫秒如果讀不到資料直接返回超時
pcap_t * handle = pcap_open(adapters->name, 65534, 1, PCAP_OPENFLAG_PROMISCUOUS, 0, 0);
if (adapters == NULL)
return;
// printf("開始偵聽: % \n", adapters->description);
pcap_pkthdr *Packet_Header; // 封包頭
const u_char * Packet_Data; // 資料本身
int retValue;
while ((retValue = pcap_next_ex(handle, &Packet_Header, &Packet_Data)) >= 0)
{
if (retValue == 0)
continue;
// printf("偵聽長度: %d \n", Packet_Header->len);
if (strcmp(Type, "ether") == 0)
{
PrintEtherHeader(Packet_Data);
}
if (strcmp(Type, "ip") == 0)
{
PrintIPHeader(Packet_Data);
}
if (strcmp(Type, "tcp") == 0)
{
PrintTCPHeader(Packet_Data);
}
if (strcmp(Type, "udp") == 0)
{
PrintUDPHeader(Packet_Data);
}
if (strcmp(Type, "icmp") == 0)
{
PrintICMPHeader(Packet_Data);
}
if (strcmp(Type, "http") == 0)
{
PrintHttpHeader(Packet_Data);
}
if (strcmp(Type, "arp") == 0)
{
PrintArpHeader(Packet_Data);
}
}
}
}
int main(int argc, char* argv[])
{
MonitorAdapter(7,"ether");
system("pause");
return 0;
}
當讀者有了上述程式碼框架,則下一步就是依次實現PrintEtherHeader
,PrintIPHeader
,PrintTCPHeader
,PrintUDPHeader
,PrintICMPHeader
,PrintHttpHeader
,PrintArpHeader
等函數,這些函數接收原始封包Packet_Data
型別,並將其轉換為對應格式的封包輸出給使用者,接下來我們將依次實現這些功能。
乙太網封包是一種在乙太網上傳送的封包格式。它通常包括乙太網頭部和乙太網資料部分。以下是它的各個部分的介紹:
乙太網頭部:包括目標MAC地址、源MAC地址以及型別/長度欄位。目標MAC地址和源MAC地址是6個位元組的二進位制數,分別表示封包的目標和來源。型別/長度欄位用於表示資料部分的長度或指定所使用的網路層協定。如果型別/長度欄位小於等於1500,則指示資料部分的長度;否則,它表示使用的協定型別。
乙太網資料部分:包括所有的上層網路協定檔頭和資料。乙太網資料部分的長度通常大於46個位元組,並且最大長度為1500個位元組。
乙太網封包通常用於在區域網上進行通訊。使用乙太網幀作為封包格式,將封包傳送到這個網路上的所有裝置。然後,目標裝置根據目標MAC地址,接收和處理這些幀,其它裝置會忽略這些幀。在乙太網封包中,目標MAC地址指的是封包要傳送到的目標裝置的唯一MAC地址,而源MAC地址則指的是傳送此訊息的裝置的MAC地址。
// 解碼資料鏈路封包 資料鏈路層為二層,解碼時只需要封裝一層ether乙太網封包頭即可.
#define hcons(A) (((WORD)(A)&0xFF00)>>8) | (((WORD)(A)&0x00FF)<<8)
void PrintEtherHeader(const u_char * packetData)
{
typedef struct ether_header
{
u_char ether_dhost[6]; // 目標地址
u_char ether_shost[6]; // 源地址
u_short ether_type; // 乙太網型別
} ether_header;
struct ether_header * eth_protocol;
eth_protocol = (struct ether_header *)packetData;
u_short ether_type = ntohs(eth_protocol->ether_type); // 乙太網型別
u_char *ether_src = eth_protocol->ether_shost; // 乙太網原始MAC地址
u_char *ether_dst = eth_protocol->ether_dhost; // 乙太網目標MAC地址
printf("型別: 0x%x \t", ether_type);
printf("原MAC地址: %02X:%02X:%02X:%02X:%02X:%02X \t",
ether_src[0], ether_src[1], ether_src[2], ether_src[3], ether_src[4], ether_src[5]);
printf("目標MAC地址: %02X:%02X:%02X:%02X:%02X:%02X \n",
ether_dst[0], ether_dst[1], ether_dst[2], ether_dst[3], ether_dst[4], ether_dst[5]);
}
由於乙太網太過於底層,所以解析乙太網我們只能得到一些基本的網路卡資訊,如下圖所示;
IP(Internet Protocol)封包是在TCP/IP(傳輸控制協定/網際網路協定)協定棧中的第三層。它通常包括IP頭部和資料部分兩部分。
IP頭部通常包括以下內容:
IP封包是在網路層傳輸的,它的主要功能是為網際網路中的各種應用程式之間提供包傳輸服務。它使用IP地址來確定封包從哪裡發出,以及封包應該被路由到達目標裝置。
在接收到IP封包時,網路裝置首先檢查封包頭的目標IP地址,然後使用路由表來找到傳輸該封包所需的下一個節點(下一跳),並將封包傳遞到該節點。如果某個路由器無法將封包傳遞到下一個節點,則該封包將被丟棄。每個節點都會檢查封包的TTL值,並將其減少1。如果TTL值變為0,則封包會被丟棄,以防止封包在網路中迴圈。
// 解碼IP封包,IP層在資料鏈路層的下面, 解碼時需要+14偏移值, 跳過資料鏈路層。
void PrintIPHeader(const u_char * packetData)
{
typedef struct ip_header
{
char version : 4;
char headerlength : 4;
char cTOS;
unsigned short totla_length;
unsigned short identification;
unsigned short flags_offset;
char time_to_live;
char Protocol;
unsigned short check_sum;
unsigned int SrcAddr;
unsigned int DstAddr;
}ip_header;
struct ip_header *ip_protocol;
// +14 跳過資料鏈路層
ip_protocol = (struct ip_header *)(packetData + 14);
SOCKADDR_IN Src_Addr, Dst_Addr = { 0 };
u_short check_sum = ntohs(ip_protocol->check_sum);
int ttl = ip_protocol->time_to_live;
int proto = ip_protocol->Protocol;
Src_Addr.sin_addr.s_addr = ip_protocol->SrcAddr;
Dst_Addr.sin_addr.s_addr = ip_protocol->DstAddr;
printf("源地址: %15s --> ", inet_ntoa(Src_Addr.sin_addr));
printf("目標地址: %15s --> ", inet_ntoa(Dst_Addr.sin_addr));
printf("校驗和: %5X --> TTL: %4d --> 協定型別: ", check_sum, ttl);
switch (ip_protocol->Protocol)
{
case 1: printf("ICMP \n"); break;
case 2: printf("IGMP \n"); break;
case 6: printf("TCP \n"); break;
case 17: printf("UDP \n"); break;
case 89: printf("OSPF \n"); break;
default: printf("None \n"); break;
}
}
針對IP層封包的解析可能會較為複雜,因為IP
協定上方可以包含ICMP,IGMP,TCP,UDP,OSPF
等協定,在執行程式後讀者會看到如下圖所示的具體資訊;
TCP(Transmission Control Protocol)層封包是在TCP/IP(傳輸控制協定/網際網路協定)協定棧中的第四層。它包括TCP頭部和資料部分兩個部分。
TCP頭部通常包括以下內容:
TCP是一個面向連線的協定,因此在傳送資料之前,TCP會先在傳送方和接收方之間建立連線。該連線建立的過程包括三次握手(three-way handshake)過程,分別是使用者端發起連線請求、伺服器發回確認、使用者端再次傳送確認。完成連線後,TCP協定根據確認號和序列號來控制封包的傳輸次序和有效性(如ACK報文的確認和重傳訊息),以提供高效的資料傳輸服務。
當TCP封包到達目標裝置後,TCP層將在接收方重新組裝TCP資料,將TCP報文分割成應用層可用的更小的資料塊,並將其傳送到目標應用程式。如果傳送的TCP協定封包未被正確地接收,則TCP協定將重新嘗試傳送丟失的封包,以確保資料的完整性和正確性。
// 解碼TCP封包,需要先加14跳過資料鏈路層, 然後再加20跳過IP層。
void PrintTCPHeader(const unsigned char * packetData)
{
typedef struct tcp_header
{
short SourPort; // 源埠號16bit
short DestPort; // 目的埠號16bit
unsigned int SequNum; // 序列號32bit
unsigned int AcknowledgeNum; // 確認號32bit
unsigned char reserved : 4, offset : 4; // 預留偏移
unsigned char flags; // 標誌
short WindowSize; // 視窗大小16bit
short CheckSum; // 檢驗和16bit
short surgentPointer; // 緊急資料偏移量16bit
}tcp_header;
struct tcp_header *tcp_protocol;
// +14 跳過資料鏈路層 +20 跳過IP層
tcp_protocol = (struct tcp_header *)(packetData + 14 + 20);
u_short sport = ntohs(tcp_protocol->SourPort);
u_short dport = ntohs(tcp_protocol->DestPort);
int window = tcp_protocol->WindowSize;
int flags = tcp_protocol->flags;
printf("源埠: %6d --> 目標埠: %6d --> 視窗大小: %7d --> 標誌: (%d)",
sport, dport, window, flags);
if (flags & 0x08) printf("PSH 資料傳輸\n");
else if (flags & 0x10) printf("ACK 響應\n");
else if (flags & 0x02) printf("SYN 建立連線\n");
else if (flags & 0x20) printf("URG \n");
else if (flags & 0x01) printf("FIN 關閉連線\n");
else if (flags & 0x04) printf("RST 連線重置\n");
else printf("None 未知\n");
}
針對TCP的解析也較為複雜,這是因為TCP協定存在多種狀態值,如PSH、ACK、SYN、URG、FIN
和RST
這些都是TCP
報文段中用於標識不同資訊或狀態的標誌位。這些TCP標誌位的含義如下:
這些標誌位的設定和使用可以幫助TCP在應用層和網路層之間進行可靠的通訊,保證資料的傳輸和連線的建立以及關閉可以正確完成,我們工具同樣可以解析這些不同的標誌位情況,如下圖所示;
UDP(User Datagram Protocol)層封包是在TCP/IP(傳輸控制協定/網際網路協定)協定棧中的第四層。它比TCP更簡單,不保證封包的位置和有效性,也不進行連線的建立和維護。UDP封包僅包含UDP頭部和資料部分。
UDP頭部包括以下內容:
UDP協定的優點是傳輸開銷小,速度快,延遲低,因為它不進行高負載的錯誤檢查,也不進行連線建立和維護。但這也意味著封包傳輸不可靠,不保證資料傳輸的完整性和正確性。如果未能正確地接收UDP封包,則不會嘗試重新傳送丟失的封包。UDP通常用於需要快速、簡單、低延遲的應用程式,例如線上遊戲、視訊和音訊串流媒體等。
// UDP層與TCP層如出一轍,僅僅只是在結構體的定義解包是有少許的不同而已.
void PrintUDPHeader(const unsigned char * packetData)
{
typedef struct udp_header
{
uint32_t sport; // 源埠
uint32_t dport; // 目標埠
uint8_t zero; // 保留位
uint8_t proto; // 協定標識
uint16_t datalen; // UDP資料長度
}udp_header;
struct udp_header *udp_protocol;
// +14 跳過資料鏈路層 +20 跳過IP層
udp_protocol = (struct udp_header *)(packetData + 14 + 20);
u_short sport = ntohs(udp_protocol->sport);
u_short dport = ntohs(udp_protocol->dport);
u_short datalen = ntohs(udp_protocol->datalen);
printf("源埠: %5d --> 目標埠: %5d --> 大小: %5d \n", sport, dport, datalen);
}
針對UDP協定的解析就變得很簡單了,因為UDP是一種無狀態協定所以只能得到源埠與目標埠,解析效果如下圖所示;
ICMP(Internet Control Message Protocol)層封包是在TCP/IP協定棧中的第三層。它是一種控制協定,用於網路通訊中的錯誤報告和網路狀態查詢。ICMP封包通常不攜帶應用資料或有效載荷。
ICMP封包通常包括以下型別的控制資訊:
ICMP封包還用於其他用途,例如Multicast Listener Discovery(MLD)和Neighbor Discovery Protocol(NDP),用於組播和IPv6網路通訊中。
ICMP資料包通常由作業系統或網路裝置自動生成,並直接傳送給作業系統或網路裝置。然後,它們可以通過網路分析工具進行檢測和診斷,以確定網路中的錯誤或故障。
// 解碼ICMP封包,在解包是需要同樣需要跳過資料鏈路層和IP層, 然後再根據ICMP型別號解析, 常用的型別號為`type 8`它代表著傳送和接收封包的時間戳。
void PrintICMPHeader(const unsigned char * packetData)
{
typedef struct icmp_header {
uint8_t type; // ICMP型別
uint8_t code; // 程式碼
uint16_t checksum; // 校驗和
uint16_t identification; // 標識
uint16_t sequence; // 序列號
uint32_t init_time; // 發起時間戳
uint16_t recv_time; // 接受時間戳
uint16_t send_time; // 傳輸時間戳
}icmp_header;
struct icmp_header *icmp_protocol;
// +14 跳過資料鏈路層 +20 跳過IP層
icmp_protocol = (struct icmp_header *)(packetData + 14 + 20);
int type = icmp_protocol->type;
int init_time = icmp_protocol->init_time;
int send_time = icmp_protocol->send_time;
int recv_time = icmp_protocol->recv_time;
if (type == 8)
{
printf("發起時間戳: %d --> 傳輸時間戳: %d --> 接收時間戳: %d 方向: ",
init_time, send_time, recv_time);
switch (type)
{
case 0: printf("回顯應答報文 \n"); break;
case 8: printf("回顯請求報文 \n"); break;
default:break;
}
}
}
針對ICMP協定的解析也很簡單在抓包時我們同樣只能得到一些基本的資訊,例如傳送時間戳,傳輸時間戳,接收時間戳,以及報文方向等,這裡的方向有兩種一種是0代表回顯應答,而8則代表回顯請求,具體輸出效果圖如下所示;
HTTP(Hypertext Transfer Protocol)層封包是在TCP/IP協定棧中的第七層,它主要用於Web應用程式中的客戶機和伺服器之間的資料傳輸。HTTP封包通常包括HTTP頭部和資料部分兩個部分。
HTTP頭部通常包括以下內容:
HTTP協定的工作方式是使用者端向伺服器傳送HTTP請求,伺服器通過HTTP響應返回請求結果。HTTP請求通常使用HTTP方法,如GET、POST、PUT、DELETE等,控制HTTP操作的型別和行為。HTTP響應通常包含HTTP狀態碼,如200、404、500等,以指示使用者端請求結果的狀態。
在實際的網路通訊中,HTTP層封包的格式和內容通常由應用程式或網路裝置生成和分析,例如Web瀏覽器和Web伺服器。
// 解碼HTTP封包,需要跳過資料鏈路層, IP層以及TCP層, 最後即可得到HTTP封包協定頭。
void PrintHttpHeader(const unsigned char * packetData)
{
typedef struct tcp_port
{
unsigned short sport;
unsigned short dport;
}tcp_port;
typedef struct http_header
{
char url[512];
}http_header;
struct tcp_port *tcp_protocol;
struct http_header *http_protocol;
tcp_protocol = (struct tcp_port *)(packetData + 14 + 20);
int tcp_sport = ntohs(tcp_protocol->sport);
int tcp_dport = ntohs(tcp_protocol->dport);
if (tcp_sport == 80 || tcp_dport == 80)
{
// +14 跳過MAC層 +20 跳過IP層 +20 跳過TCP層
http_protocol = (struct http_header *)(packetData + 14 + 20 + 20);
printf("%s \n", http_protocol->url);
}
}
針對HTTP協定的解析同樣可以,但由於HTTP協定已經用的很少了所以這段程式碼也只能演示,在實戰中一般會使用HTTPS,如下則是一個HTTP存取時捕獲的封包;
ARP(Address Resolution Protocol)層封包是在TCP/IP協定棧中的第二層。ARP協定主要用於將網路層地址(如IP地址)對映到資料鏈路層地址(如MAC地址)。
ARP封包通常包括以下內容:
ARP協定工作的過程如下:
ARP協定的工作主要是在本地網路中實現地址對映,主要包括確定哪個裝置的MAC地址與特定的IP地址關聯,以及應答IP地址轉化成相應的MAC地址的對映請求。ARP通常用於乙太網和WiFi網路中,以實現區域網內的裝置通訊。
// 解碼ARP封包
void PrintArpHeader(const unsigned char * packetData)
{
typedef struct arp_header
{
uint16_t arp_hardware_type;
uint16_t arp_protocol_type;
uint8_t arp_hardware_length;
uint8_t arp_protocol_length;
uint16_t arp_operation_code;
uint8_t arp_source_ethernet_address[6];
uint8_t arp_source_ip_address[4];
uint8_t arp_destination_ethernet_address[6];
uint8_t arp_destination_ip_address[4];
}arp_header;
struct arp_header *arp_protocol;
arp_protocol = (struct arp_header *)(packetData + 14);
u_short hardware_type = ntohs(arp_protocol->arp_hardware_type);
u_short protocol_type = ntohs(arp_protocol->arp_protocol_type);
int arp_hardware_length = arp_protocol->arp_hardware_length;
int arp_protocol_length = arp_protocol->arp_protocol_length;
u_short operation_code = ntohs(arp_protocol->arp_operation_code);
// 判讀是否為ARP請求包
if (arp_hardware_length == 6 && arp_protocol_length == 4)
{
printf("原MAC地址: ");
for (int x = 0; x < 6; x++)
printf("%x:", arp_protocol->arp_source_ethernet_address[x]);
printf(" --> ");
printf("目標MAC地址: ");
for (int x = 0; x < 6; x++)
printf("%x:", arp_protocol->arp_destination_ethernet_address[x]);
printf(" --> ");
switch (operation_code)
{
case 1: printf("ARP 請求 \n"); break;
case 2: printf("ARP 應答 \n"); break;
case 3: printf("RARP 請求 \n"); break;
case 4: printf("RARP 應答 \n"); break;
default: break;
}
}
}
解析ARP協定同樣可以實現,ARP協定同樣有多個狀態,一般1-2
代表請求與應答,3-4
代表RARP反向請求與應答,ARP協定由於觸發週期短所以讀者可能很少捕捉到這類資料,如下圖時讀者捕捉到的一條完整的ARP協定狀態;
本文作者: 王瑞
本文連結: https://www.lyshark.com/post/526b8a6.html
版權宣告: 本部落格所有文章除特別宣告外,均採用 BY-NC-SA 許可協定。轉載請註明出處!