C/C++ 原生套接字抓取FTP數據包

網絡通信在今天的信息時代中扮演着至關重要的角色,而對網絡數據包進行捕獲與分析則是網絡管理、網絡安全等領域中不可或缺的一項技術。本文將深入介紹基於原始套接字的網絡數據包捕獲與分析工具,通過實時監控網絡流量,實現抓取流量包內的FTP通信數據,並深入瞭解數據傳輸的細節,捕捉潛在的網絡問題以及進行安全性分析。

原始套接字是一種底層的網絡編程方式,允許程序直接訪問網絡協議棧,無需操作系統進行任何處理。在Windows平臺,可以通過SOCK_RAW套接字類型來創建原始套接字。本文的代碼示例基於Winsock2庫實現,允許我們以最底層的方式捕獲網絡數據包。

Winsock2庫與套接字初始化

在使用原始套接字之前,我們首先需要初始化Winsock2庫。Winsock2提供了在Windows平臺上進行套接字編程所需的函數和結構。代碼中的WSAStartup函數完成了Winsock2庫的初始化工作。

#include <winsock2.h>
#include <stdio.h>
#include <mstcpip.h>

#pragma comment(lib, "Advapi32.lib")
#pragma comment (lib, "ws2_32")

WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    return -1;

數據包結構解析

接着我們需要定義數據包結構,常見的協議頭結構:IP(Internet Protocol)頭、TCP(Transmission Control Protocol)頭和UDP(User Datagram Protocol)頭。如果想要解析TCP/UDP頭則需要先來解析IP頭,並依次向下解析。

IP頭

IP頭是互聯網通信中用於標識數據報的頭部信息。下面是IP頭的結構:

typedef struct _IPHeader {
  UCHAR     iphVerLen;        // 版本號和頭長度(各佔4位)
  UCHAR     ipTOS;            // 服務類型
  USHORT    ipLength;         // 封包總長度,即整個IP報的長度
  USHORT    ipID;             // 封包標識,惟一標識發送的每一個數據報
  USHORT    ipFlags;          // 標誌
  UCHAR     ipTTL;            // 生存時間,即TTL
  UCHAR     ipProtocol;       // 協議,可能是TCP、UDP、ICMP等
  USHORT    ipChecksum;       // 校驗和
  ULONG     ipSource;         // 源IP地址
  ULONG     ipDestination;    // 目標IP地址
} IPHeader, *PIPHeader;

在IP頭中,我們可以獲取到源IP地址、目標IP地址、數據包長度、生存時間(TTL)、協議類型等信息。IP頭的版本號和頭長度字段結合在一起,佔4位,用於表示IP協議的版本和IP頭的長度。協議字段指示了數據包中的上層協議類型,例如TCP、UDP或ICMP。

TCP頭

TCP是一種面向連接的協議,它提供可靠的、字節流的通信。TCP頭包含了一系列關鍵的信息,用於控制數據傳輸的各個方面。下面是TCP頭的結構:

typedef struct _TCPHeader {
  USHORT  sourcePort;         // 16位源端口號
  USHORT  destinationPort;    // 16位目的端口號
  ULONG   sequenceNumber;     // 32位序列號
  ULONG   acknowledgeNumber;  // 32位確認號
  UCHAR   dataoffset;         // 高4位表示數據偏移
  UCHAR   flags;              // 6位標誌位    
  USHORT  windows;            // 16位窗口大小
  USHORT  checksum;           // 16位校驗和
  USHORT  urgentPointer;      // 16位緊急數據偏移量 
} TCPHeader, *PTCPHeader;

TCP頭中的源端口號和目的端口號標識了數據包的發送和接收方。序列號和確認號用於維護連接的狀態。標誌位字段包括了TCP協議中的各種控制信息,如SYN、ACK、FIN等。窗口大小表示接收方當前願意接收的數據量。

UDP頭

UDP是一種無連接的協議,它提供了簡單的、不可靠的數據傳輸。UDP頭相比TCP頭較爲簡單,但同樣包含了一些關鍵的信息。下面是UDP頭的結構:

typedef struct _UDPHeader {
  USHORT      sourcePort;       // 源端口號   
  USHORT      destinationPort;  // 目的端口號    
  USHORT      len;              // 封包長度
  USHORT      checksum;         // 校驗和
} UDPHeader, *PUDPHeader;

UDP頭中的源端口號和目的端口號同樣標識了數據包的發送和接收方。封包長度字段表示UDP包的總長度,包括UDP頭和數據部分。校驗和字段用於檢測數據包的完整性。

創建原始套接字

使用socket函數創建原始套接字,指定協議爲IPPROTO_IP,表示接收所有的IP包。

SOCKET SockRaw = socket(AF_INET, SOCK_RAW, IPPROTO_IP);

綁定本地IP地址

爲了接收網絡數據包,我們需要綁定本地IP地址。通過gethostbyname函數獲取本地主機名,並使用bind函數綁定套接字與本地地址。

struct hostent* pHost;
gethostname(szHostName, 56);
if ((pHost = gethostbyname(szHostName)) == NULL)
    return -1;

addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(0);
memcpy(&addr_in.sin_addr.S_un.S_addr, pHost->h_addr_list[0], pHost->h_length);

if (bind(SockRaw, (PSOCKADDR)&addr_in, sizeof(addr_in)) == SOCKET_ERROR)
    return -1;

開啓混雜模式

通過ioctlsocket函數調用SIO_RCVALL控制代碼,開啓混雜模式,接收所有的IP包。

DWORD dwValue = 1;
if (ioctlsocket(SockRaw, SIO_RCVALL, &dwValue) != 0)
    return -1;

實時接收與解析數據包

使用recv函數接收數據包,根據協議類型進行解析。本文示例中僅對TCP和UDP進行了簡單的解析,可以根據實際需要擴展解析功能。

while (TRUE)
{
    nRet = recv(SockRaw, buff, 1024, 0);
    if (nRet > 0)
    {
        DecodeIPPacket(buff);
    }
}

解析IP包

根據IP包的協議類型,將數據包傳遞給相應的解析函數。

void DecodeIPPacket(char* pData)
{
    IPHeader* pIPHdr = (IPHeader*)pData;
    // ...
    switch (pIPHdr->ipProtocol)
    {
    case IPPROTO_TCP:
        DecodeTCPPacket(pData + nHeaderLen, szSourceIp, szDestIp);
        break;
    case IPPROTO_UDP:
        DecodeUDPPacket(pData + nHeaderLen, szSourceIp, szDestIp);
        break;
    }
}

解析TCP包與UDP包

根據TCP或UDP包的特徵進行解析,例如獲取源端口、目標端口等信息。

void DecodeTCPPacket(char* pData, char* szSrcIP, char* szDestIp)
{
    TCPHeader* pTCPHdr = (TCPHeader*)pData;
    // ...
}

void DecodeUDPPacket(char* pData, char* szSrcIP, char* szDestIp)
{
    UDPHeader* pUDPHdr = (UDPHeader*)pData;
    // ...
}

實時監控網絡流量

通過以上步驟,我們實現了一個簡單的網絡數據包捕獲工具。該工具可以實時監控網絡流量,解析TCP和UDP包,並輸出源地址、目標地址、端口信息以及TCP的狀態等信息,完整代碼如下;

#include <winsock2.h>
#include <stdio.h>
#include <mstcpip.h>

#pragma comment(lib, "Advapi32.lib")
#pragma comment (lib, "ws2_32")

typedef struct _IPHeader      // 20字節的IP頭
{
  UCHAR     iphVerLen;      // 版本號和頭長度(各佔4位)
  UCHAR     ipTOS;          // 服務類型
  USHORT    ipLength;       // 封包總長度,即整個IP報的長度
  USHORT    ipID;       // 封包標識,惟一標識發送的每一個數據報
  USHORT    ipFlags;        // 標誌
  UCHAR     ipTTL;        // 生存時間,就是TTL
  UCHAR     ipProtocol;     // 協議,可能是TCP、UDP、ICMP等
  USHORT    ipChecksum;     // 校驗和
  ULONG     ipSource;       // 源IP地址
  ULONG     ipDestination;  // 目標IP地址
} IPHeader, *PIPHeader;

typedef struct _TCPHeader   // 20字節的TCP頭
{
  USHORT  sourcePort;     // 16位源端口號
  USHORT  destinationPort;  // 16位目的端口號
  ULONG sequenceNumber;   // 32位序列號
  ULONG acknowledgeNumber;  // 32位確認號
  UCHAR dataoffset;     // 高4位表示數據偏移
  UCHAR flags;        // 6位標誌位    
  USHORT  windows;      // 16位窗口大小
  USHORT  checksum;     // 16位校驗和
  USHORT  urgentPointer;    // 16位緊急數據偏移量 
} TCPHeader, *PTCPHeader;

typedef struct _UDPHeader
{
  USHORT      sourcePort;   // 源端口號   
  USHORT      destinationPort;// 目的端口號    
  USHORT      len;      // 封包長度
  USHORT      checksum;   // 校驗和
} UDPHeader, *PUDPHeader;

void DecodeTCPPacket(char *pData, char *szSrcIP, char *szDestIp)
{
  TCPHeader *pTCPHdr = (TCPHeader *)pData;

  printf("[TCP] 源地址: %15s:%5d --> 目標地址: %15s:%5d 狀態: ",
    szSrcIP,ntohs(pTCPHdr->sourcePort),szDestIp,ntohs(pTCPHdr->destinationPort));
  
  switch (pTCPHdr->flags)
  {
  case 0x1: printf("TCP_FIN \n"); break;
  case 0x2: printf("TCP_SYN \n"); break;
  case 0x4: printf("TCP_RST \n"); break;
  case 0x8: printf("TCP_PSH \n"); break;
  case 0x10: printf("TCP_ACK \n"); break;
  default:printf("None \n"); break;
  }

  // 根據端口號判斷協議類型
  switch (ntohs(pTCPHdr->destinationPort))
  {
  case 21:
    // 解析FTP的用戶名和密碼
    pData = pData + sizeof(TCPHeader);
    if (strncmp(pData, "USER ", 5) == 0)
      printf("FTP用戶名: %s \n", pData + 4);
    if (strncmp(pData, "PASS ", 5) == 0)
      printf("FTP密碼: %s \n", pData + 4);
    break;
  case 80:
    printf("%s \n", pData + sizeof(TCPHeader));
    break;
  }
}

void DecodeUDPPacket(char *pData, char *szSrcIP, char *szDestIp)
{
  UDPHeader *pUDPHdr = (UDPHeader *)pData;

  printf("[UDP] 源地址: %15s:%5d --> 目標地址: %15s:%5d \n",
    szSrcIP,ntohs(pUDPHdr->sourcePort),szDestIp,ntohs(pUDPHdr->destinationPort));
}

void DecodeIPPacket(char *pData)
{
  IPHeader *pIPHdr = (IPHeader*)pData;

  in_addr source, dest = {0};
  char szSourceIp[32], szDestIp[32];

  // 從IP頭中取出源IP地址和目的IP地址
  source.S_un.S_addr = pIPHdr->ipSource;
  dest.S_un.S_addr = pIPHdr->ipDestination;
  strcpy(szSourceIp, inet_ntoa(source));
  strcpy(szDestIp, inet_ntoa(dest));

  // IP頭長度
  int nHeaderLen = (pIPHdr->iphVerLen & 0xf) * sizeof(ULONG);

  switch (pIPHdr->ipProtocol)
  {
  case IPPROTO_TCP:  // 如果是TCP協議,則繼續解析
    DecodeTCPPacket(pData + nHeaderLen, szSourceIp, szDestIp);
    break;
  case IPPROTO_UDP:  // UDP協議的解析
    DecodeUDPPacket(pData + nHeaderLen, szSourceIp, szDestIp);
    break;
  }
}

int main(int argc, char* argv[])
{
  WSADATA  wsa;

  if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    return -1;

  // 創建原始套接字,過濾IP數據包
  SOCKET SockRaw = socket(AF_INET, SOCK_RAW, IPPROTO_IP);

  // 獲取本地IP地址
  char szHostName[56];
  SOCKADDR_IN addr_in;
  struct hostent *pHost;
  gethostname(szHostName, 56);
  if ((pHost = gethostbyname((char*)szHostName)) == NULL)
    return -1;

  // 在調用ioctl之前,套節字必須綁定
  addr_in.sin_family = AF_INET;
  addr_in.sin_port = htons(0);
  // 此處的網卡pHost->h_addr_list[0] 不同機器序號不同
  memcpy(&addr_in.sin_addr.S_un.S_addr, pHost->h_addr_list[0], pHost->h_length);
  printf("綁定IP地址爲: %s \n", inet_ntoa(addr_in.sin_addr));

  if (bind(SockRaw, (PSOCKADDR)&addr_in, sizeof(addr_in)) == SOCKET_ERROR)
    return -1;

  // 設置SIO_RCVALL控制代碼,接收所有的IP包
  DWORD dwValue = 1;
  if (ioctlsocket(SockRaw, SIO_RCVALL, &dwValue) != 0)
    return -1;

  // 開始接收封包
  char buff[4096];
  int nRet;
  while (TRUE)
  {
    nRet = recv(SockRaw, buff, 1024, 0);
    if (nRet > 0)
    {
      DecodeIPPacket(buff);
    }
  }
  closesocket(SockRaw);
  WSACleanup();
  return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章