NPCAP 庫是一種用於在`Windows`平臺上進行網路數據包捕獲和分析的庫。它是`WinPcap`庫的一個分支,由`Nmap`開發團隊開發,併在`Nmap`軟體中使用。與`WinPcap`一樣,NPCAP庫提供了一些`API`,使開發人員可以輕鬆地在其應用程式中捕獲和處理網路數據包。NPCAP庫... ...
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);
其中,參數含義如下:
- source:指定遠程介面的IP地址,或者為本地介面傳入NULL。
- auth:一個指向
pcap_rmtauth
結構來指定遠程的IP和用戶名。 - alldevsp:一個指向指針,返回主機上可用的設備列表。
- errbuf:一個用於存儲錯誤信息的緩衝區。
該函數允許開發者通過一個結構來檢索所有網路適配器的詳細信息。它允許指定一個過濾器,以匹配用戶定義的網路適配器和屬性。此外,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);
其參數含義如下:
- source:要打開的網路介面的名稱或者是保存在
pcap_open_live()
中獲取的名稱。 - snaplen:設置捕獲數據包的大小。
- flags:設置捕獲數據包的模式,在
promiscuous
控制器模式或非promiscuous
模式下捕獲。 - read_timeout:設置阻塞讀函數的超時時間以毫秒為單位。
- auth:一個指向
pcap_rmtauth
結構,指定遠程的IP和用戶名。 - errbuf:一個用於存儲錯誤信息的緩衝區。
該函數返回一個指向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);
參數含義如下:
- p:指向
pcap_t
類型結構體的指針,代表打開的網路適配器。 - pkt_header:一個指向指向
pcap_pkthdr
類型的指針,該類型結構體包含有關當前數據包的元數據,例如時間戳、數據包長度、捕獲到數據包的網路適配器介面等。 - pkt_data:一個指向被捕獲的數據包的指針。
它返回以下三種返回值之一:
- 1:成功捕獲一個數據包,
pkt_header
和pkt_data
則指向相關信息; - 0:在指定的時間內未捕獲到任何數據包;
- -1:發生錯誤,導致無法從網路適配器讀取數據包。此時可以在
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層數據包
IP(Internet Protocol)數據包是在TCP/IP(傳輸控制協議/互聯網協議)協議棧中的第三層。它通常包括IP頭部和數據部分兩部分。
IP頭部通常包括以下內容:
- 版本號:表示所使用的IP協議版本號。
- 頭部長度:表示整個IP頭部的長度。TCP/IP協議中的長度都以位元組(byte)為單位計數。
- 總長度:表示整個IP數據包的長度,包括頭部和有效負載部分。
- TTL:生存時間,用於限制路由器轉發該數據包的次數。
- 協議:表示上層使用的協議類型。
- 源IP地址:發送該數據包的設備的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層數據包
TCP(Transmission Control Protocol)層數據包是在TCP/IP(傳輸控制協議/互聯網協議)協議棧中的第四層。它包括TCP頭部和數據部分兩個部分。
TCP頭部通常包括以下內容:
- 源埠號:表示發送該數據包的應用程式的埠號。
- 目的埠號:表示接收該數據包的應用程式的埠號。
- 序列號:用於將多個數據包排序,確保它們在正確的順序中到達接收方應用程式。
- 確認號:用於確認接收方已經成功收到序列號或最後一個被成功接收的數據包。
- ACK和SYN標誌:這些是TCP頭部中的標誌位,用於控制TCP連接的建立和關閉。
- 視窗大小:用於控制數據流發送的速率,並確保不會發送太多的數據包,導致網路擁塞。
- 校驗和:用於校驗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標誌位的含義如下:
- PSH(Push):該標誌位表示接收端應用程式應立即從接收緩存中讀取數據。通常在發送方需要儘快將所有數據發送給接收方時使用。
- ACK(Acknowledgment):該標誌位表示應答。用於確認已經成功接收到別的TCP包。在TCP連接建立完成後,所有TCP報文段都必須設置ACK標誌位。
- SYN(Synchronous):該標誌位用於建立TCP連接。指示請求建立一個連接,同時序列號以隨機數ISN開始。發送SYN報文的一端會進入SYN_SENT狀態。
- URG(Urgent):該標誌位表示緊急指針有效。它用於告知接收端在此報文段中存在緊急數據,緊急數據應該立即送達接收端的應用層。
- FIN(Finish):此標誌用於終止TCP連接。FIN標誌位被置位的一端表明它已經發送完所有數據並要求釋放連接。
- RST(Reset):該標誌用於重置TCP連接。當TCP連接嘗試建立失敗,或一個已關閉的套接字收到數據,都會發送帶RST標誌的數據包。
這些標誌位的設置和使用可以幫助TCP在應用層和網路層之間進行可靠的通信,保證數據的傳輸和連接的建立以及關閉可以正確完成,我們工具同樣可以解析這些不同的標誌位情況,如下圖所示;
解碼UDP層數據包
UDP(User Datagram Protocol)層數據包是在TCP/IP(傳輸控制協議/互聯網協議)協議棧中的第四層。它比TCP更簡單,不保證數據包的位置和有效性,也不進行連接的建立和維護。UDP數據包僅包含UDP頭部和數據部分。
UDP頭部包括以下內容:
- 源埠號:表示發起該數據包的應用程式的埠號。
- 目的埠號:表示接收該數據包的應用程式的埠號。
- 數據長度:表示數據包中包含的數據長度。
- 校驗和:用於校驗UDP頭部和數據部分是否被損壞或篡改。
- 數據部分和TCP層數據包類似,是上層應用程式傳遞到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層數據包
ICMP(Internet Control Message Protocol)層數據包是在TCP/IP協議棧中的第三層。它是一種控制協議,用於網路通信中的錯誤報告和網路狀態查詢。ICMP數據包通常不攜帶應用數據或有效載荷。
ICMP數據包通常包括以下類型的控制信息:
- Echo Request/Reply: 用於網路連通性測試,例如ping命令(12/0)
- Destination unreachable: 該類型的ICMP數據包用於向發送者傳遞對目標無法到達的消息(3/0、3/1、3/2、3/3、3/4、3/5、3/6、3/7、3/8、3/9、3/10)
- Redirect: 用於告知發送方使用新的路由器來發送數據(5/0、5/1、5/2)
- Time exceeded: 用於向發送方報告基於TTL值無法到達目的地,表示躍點數超過了最大限制(11/0、11/1)
- Parameter problem: 用於向發送者報告轉發器無法處理IP數據包中的某些欄位(12/0)
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層數據包
HTTP(Hypertext Transfer Protocol)層數據包是在TCP/IP協議棧中的第七層,它主要用於Web應用程式中的客戶機和伺服器之間的數據傳輸。HTTP數據包通常包括HTTP頭部和數據部分兩個部分。
HTTP頭部通常包括以下內容:
- 請求行:用於描述客戶機發起的請求。
- 響應行:用於描述伺服器返回的響應。
- 頭部欄位:用於向請求或響應添加額外的元數據信息,例如HTTP版本號、日期、內容類型等。
- Cookie:用於在客戶端和伺服器之間來保存狀態信息。
- Cache-Control:用於客戶端和伺服器之間控制緩存的行為。
- 數據部分是包含在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層數據包
ARP(Address Resolution Protocol)層數據包是在TCP/IP協議棧中的第二層。ARP協議主要用於將網路層地址(如IP地址)映射到數據鏈路層地址(如MAC地址)。
ARP數據包通常包括以下內容:
- ARP請求或響應:ARP請求用於獲取與IP地址關聯的MAC地址,而ARP響應用於提供目標MAC地址。
- 發送者的MAC地址:發送ARP請求或響應的設備的MAC地址。
- 發送者的IP地址:發送ARP請求或響應的設備的IP地址。
- 目標的MAC地址:目標設備的MAC地址。
- 目標的IP地址:目標設備的IP地址。
ARP協議工作的過程如下:
- 發送者主機發送一個ARP請求,包含目標IP地址。
- 網路中的所有設備都收到該ARP請求。
- 如果有設備的IP地址與ARP請求中的目標IP地址匹配,該設備會回覆ARP響應,包含自己的MAC地址。
- 發送者主機使用響應中的MAC地址來與該設備通信。
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 許可協議。轉載請註明出處!
版權聲明:本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!