首页 > 编程语言 >C/C++ 原生套接字抓取FTP数据包

C/C++ 原生套接字抓取FTP数据包

时间:2023-12-04 20:03:44浏览次数:34  
标签:FTP UDP addr IP USHORT pData TCP C++ 数据包

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

标签:FTP,UDP,addr,IP,USHORT,pData,TCP,C++,数据包
From: https://www.cnblogs.com/LyShark/p/17875792.html

相关文章

  • C++:如何将 LLVM 嵌套到你的项目中去
    IDE:ClionLLVMcmake_minimum_required(VERSION3.9)project(clang_demo)find_package(LLVMREQUIREDCONFIG)message(STATUS"FoundLLVM${LLVM_PACKAGE_VERSION}")message(STATUS"UsingLLVMConfig.cmakein:${LLVM_DIR}")......
  • C++_线程池代码看C++类-模板-标准库
    C++线程池线程池的组成部分:线程池管理器(ThreadPoolManager):用于创建并管理线程池工作线程(WorkThread):线程池中线程任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。任务队列:用于存放没有处理的任务。提供一种缓冲机制。 通过新......
  • 一. C++基础
    文章参考:《C++面向对象程序设计》✍千处细节、万字总结(建议收藏)_白鳯的博客-CSDN博客1.一个简单的案例#include<iostream>//编译预处理命令usingnamespacestd;//使用命名空间intadd(inta,intb);//函数原型说明intmain()//主函数{ intx,y;......
  • 2021 最佳 C++ IDE 排行
    2021最佳C++IDE排行 BlogAuthor:DoriExtermanPublishedOn:5月31,2021Estimatedreadingtime:1minutes想把所有优秀的 IDE(集成开发环境)或类似 IDE 的工具在一篇文章内梳理出来,比登天还难。不过,JetBrains的调查数据显示,75% 的受访者经常使用 IDE。这些工具推......
  • 最佳 C++ 编译器
    最佳C++编译器Incredibuild​已认证账号​关注 134人赞同了该文章 C++是一个“开放”的编程语言,任何人都可以使用自己喜欢的编译器。当然,C++编译器的种类也很多。同样, C++IDE 也不少,我在之前的一个博客中讨论过这个话题。编译和运行C++......
  • C++ 内联函数 inline
    宏定义实现和普通函数实现:-宏定义是直接在实现的时候进行代码替换,可能产生结果异常问题。-普通函数实现:调用函数进出函数体的时候时间开销可能过大。1#include<iostream>2usingnamespacestd;3//宏实现4#defineGETMAX(a,b)((a)>(b)?(a):(b))......
  • c++ friend关键字 友元
     在C++中,friend关键字用于声明友元函数或友元类1。友元函数或友元类可以访问当前类的私有成员和保护成员,即使它们不是当前类的成员函数或成员类1。友元函数是一个独立的函数,而友元类是一个类可以访问另一个类的私有成员和保护成员1。例如,如果要声明函数为一个类的友元,需要......
  • Java实现FTP文件上传和下载
    目录一、背景二、maven依赖三、FTP工具类3.1、主动模式(PORT)3.2、被动模式(PASV)四、验证4.1、dos下操作FTP4.2、FTP文件上传4.3、FTP文件下载一、背景  我在之前的文章(Java实现文件上传和下载)里讲过非FTP文件的上传和下载,今天我们来讲一下FTP文件上传和下载,本文测试过程中SpringB......
  • C++U5-08-二叉树1
    上节课作业分析讲解视频链接:https://pan.baidu.com/s/1_jaM_TlZmLJX4JbLuJtKzA?pwd=2us4提取码:2us4学习目标  树在C++中,二叉树是一种常用的数据结构,由节点(Node)组成,每个节点可以有最多两个子节点。二叉树具有以下几个主要的作用:存储和组织数据:二叉树可用于存储和组织大......
  • C++/Filesystem 文件类型
    #include<iostream>#include<filesystem>#include<string>namespacefs=std::filesystem;voiddemo_status(constfs::path&p,fs::file_statuss){std::cout<<p;switch(s.type()){casefs::file_type::none:......