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; }
以上就是C/C++利用原生套接字抓取FTP数据包的详细内容,更多关于C++套接字抓取FTP数据包的资料请关注脚本之家其它相关文章!