网络通信在今天的信息时代中扮演着至关重要的角色,而对网络数据包进行捕获与分析则是网络管理、网络安全等领域中不可或缺的一项技术。本文将深入介绍基于原始套接字的网络数据包捕获与分析工具,通过实时监控网络流量,实现抓取流量包内的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数据包的更多相关文章

  1. 用C++实现网络编程---抓取网络数据包的实现方法

    一般都熟悉sniffer这个工具,它可以捕捉流经本地网卡的所有数据包.抓取网络数据包进行分析有很多用处,如分析网络是否有网络病毒等异常数据,通信协议的分析(数据链路层协议.IP.UDP.TCP.甚至各 ...

  2. Charles 如何抓取https数据包

    Charles可以正常抓取http数据包,但是如果没有经过进一步设置的话,无法正常抓取https的数据包,通常会出现乱码.举个例子,如果没有做更多设置,Charles抓取https://www.bai ...

  3. Fiddler:在PC和移动设备上抓取HTTPS数据包

    Fiddler是一个免费的Web调试代理,支持任何浏览器.系统以及平台.这个工具是进行Web和App网络开发的必备工具,戳此处下载. 根据Fiddler官网的描述,具有以下六大特点: Web调试 性能 ...

  4. Android利用tcpdump和wireshark抓取网络数据包

    Android利用tcpdump和wireshark抓取网络数据包 主要介绍如何利用tcpdump抓取andorid手机上网络数据请求,利用Wireshark可以清晰的查看到网络请求的各个过程包括三次 ...

  5. 图解Fiddler如何抓取Android数据包

    介绍Fiddler抓取Android数据包希望对大家的工作和学习有所帮助! 电脑开启wifi热点 首先在电脑上下载一个wifi软件,我这里用的是猎豹wifi,电脑开启wifi热点后,如下图所示:  设 ...

  6. Fiddler抓取https数据包

    Wireshark和Fiddler的优缺点: ①Wireshark是一种在网络层上工作的抓包工具,不仅自带大量的协议分析器,而且可以通过编写Wireshark插件来识别自定义的协议.虽然Wiresha ...

  7. Fiddler基础用法-抓取浏览器数据包

    Fiddler基础知识 Fiddler是强大的抓包工具,它的原理是以web代理服务器的形式进行工作的,使用的代理地址是:127.0.0.1,端口默认为8888,我们也可以通过设置进行修改. 代理就是在 ...

  8. wireshark抓取本地数据包

    windows系统中,本地向自身发送数据包没有经过真实的网络接口,而是通过环路(loopback interface)接口发送,所以使用基于只能从真实网络接口中抓数据的winpcap是无法抓取本地数据 ...

  9. wireshark在windows下无法抓取localhost数据包

    在调试SSL时要抓包,通过tcpview和minisniffer等工具明明看到tcp连接已经建立并开始收发数据了,但wireshark却总是无法抓到相应的数据包. 今天早上,HQ的高工告诉我“wire ...

  10. wireshark抓取OpenFlow数据包

    在写SDN控制器应用或者改写控制器源码的时候,经常需要抓包,验证网络功能,以及流表的执行结果等等,wireshark是个很好的抓包分析包的网络工具,下面简介如何用wireshark软件抓取OpenFl ...

随机推荐

  1. Spring事务传播机制解析

    确保数据一致性的关键 在Java的Spring框架中,事务管理是保证应用数据一致性和可靠性的关键.Spring提供了灵活的事务传播机制,它定义了事务边界,以及在嵌套方法调用时如何处理事务.本文旨在深入 ...

  2. ChatGPT插件权限给Plus用户放开了

    大家好,我是章北海mlpy ChatGPT插件权限给Plus用户放开了 我稍微测试了俩,感觉还行,后续我会对一些热门插件深入测测,敬请期待. 官方对插件的介绍如下: 1.插件由非由OpenAI控制的第 ...

  3. 100天搞定机器学习|Day60 遇事不决,XGBoost

    XGBoost 是一种集大成的机器学习算法,可用于回归,分类和排序等各种问题,在机器学习大赛及工业领域被广泛应用.成功案例包括:网页文本分类.顾客行为预测.情感挖掘.广告点击率预测.恶意软件分类.物品 ...

  4. JSP 学习笔记 | 二、JSP 脚本 & 案例实现 & 缺点分析

    前文:JSP 学习笔记 | 一.JSP 原理理解 JSP脚本用于在 JSP页面内定义 Java代码.很多入门案例中我们就在 JSP 页面定义的 Java 代码就是 JSP 脚本. JSP 脚本分类 J ...

  5. 五分钟,手撸一个简单的Spring容器

    工厂和Spring容器Spring是一个成熟的框架,为了满足扩展性.实现各种功能,所以它的实现如同枝节交错的大树一样,现在让我们把视线从Spring本身移开,来看看一个萌芽版的Spring容器怎么实现 ...

  6. 基于阿里云Serverless函数计算开发的疫情数据统计推送机器人

    一.Serverless函数计算 什么是Serverless? 在<Serverless Architectures>中对 Serverless 是这样子定义的: Serverless w ...

  7. 拥抱开放,Serverless 时代的下一征程

    Serverless 作为云计算的最佳实践和未来演进趋势,其全托管免运维的使用体验和按量付费的成本优势使得它在云原生时代备受推崇.Serverless 的使用场景也由事件驱动,数据处理等部分特定场景转 ...

  8. vue 文件路径获取文件名

    例如: url 是//resource//20220819//kfz//调试.zip转换后结果为 调试.zip//文件路径获取文件名 getFileName(url) { let name = &qu ...

  9. HTTP Headers Content-Type 详解

    https://www.cnblogs.com/whosmeya/p/14315632.html

  10. 基于Tensorflow技术开发的计算机毕业设计辅助生成器(使用AI大模型技术)

    这是一个辅助生成计算机毕业设计的工具,可以自动完成毕业设计的源码.它基于几百个github上面开源的java和python项目,运用tensorflow技术,训练出了AI大模型.基本实现了计算机毕业设 ...