18.2 使用NPCAP庫抓取數據包

来源:https://www.cnblogs.com/LyShark/archive/2023/10/26/17788576.html
-Advertisement-
Play Games

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當下載後讀者可自行點擊下一步即可,當安裝完成後即可看到如下圖所示的提示信息;

當驅動程式安裝完成後,讀者就可以自行配置開發工具包到項目中,通常只需要將工具包內的includelib庫配置到項目中即可,如下圖所示配置後自行應用保存即可。

接著我們來實現第一個功能,枚舉當前主機中可以使用的網卡信息,該功能的實現主要依賴於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_headerpkt_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、FINRST這些都是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 許可協議。轉載請註明出處!

文章出處:https://www.cnblogs.com/LyShark/p/17788576.html
版權聲明:本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 9.1、環境搭建 9.1.1、在project創建新module 9.1.2、選擇maven 9.1.3、設置module名稱和路徑 9.1.4、module初始狀態 9.1.5、配置打包方式和引入依賴 註意:預設的打包方式為 jar,為了能配置web資源,需要將打包方式設置為 war <?xml ...
  • 1. maven打包方式 maven打包有三種方式 pom、jar、war。在pom.xml 文件中聲明的方式分別如下: <!-- 1. pom方式 --> <packaging>pom</packaging>` <!-- 2. jar方式 --> <packaging>jar</packaging ...
  • 不,代碼是值錢的! 前幾天我們一直服務的一個客戶覺得自己用了兩三年的UI太醜,乞求我們換一套。集團領導討論後一口報價30w,牛逼哄哄說:很麻煩的啊,要先設計UI庫,然後把所有頁面都換個樣,又要測試這玩意(內行人都明白前端能測出啥bug,也就可能要考慮優化),大概要6個人做一個月。 然後我這架構大頭兵 ...
  • 說明 介紹 該腳本使用Selenium庫來實現自動登錄併在指定的時間購買商品。 運行前準備 mac 的safari瀏覽器本身已經集成了safaridriver,只要啟用並開啟即可,步驟如下: 終端啟用safaridriver: sudo safaridriver --enable 嘗試運行safra ...
  • 在查找二叉樹某個節點時,如果把二叉樹所有節點理理解為解空間,待找到那個節點理解為滿足特定條件的解,對此解答可以抽象描述為: _在解空間中搜索滿足特定條件的解_,這其實就是搜索演算法(Search)的一種描述。當然也有其他描述,比如是“指一類用於在數據集合中查找特定項或解決問題的演算法”,又或者是“指通過... ...
  • 相信大家對python-docx這個常用的操作docx文檔的庫都不陌生,它支持以內聯形狀(Inline Shape)的形式插入圖片,即圖片和文本之間沒有重疊,遵循流動版式(flow layout)。但是,截至最新的0.8.10版本,python-docx尚不支持插入浮動圖片(floating pic ...
  • Bean在Spring中的定義是_org.springframework.beans.factory.config.BeanDefinition_介面,BeanDefinition裡面存儲的就是我們編寫的Java類在Spring中的元數據 ...
  • 前言 最近博主在位元組面試中遇到這樣一個面試題,這個問題也是前端面試的高頻問題,因為在前端開發的日常開發中我們總是會與post請求打交道,一個小小的post請求也是牽扯到很多知識點的,博主在這給大家細細道來。 同源策略 在瀏覽器中,內容是很開放的,任何資源都可以接入其中,如 JavaScript 文件 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...