18.1 Socket 原生套接字抓包

这篇具有很好参考价值的文章主要介绍了18.1 Socket 原生套接字抓包。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

原生套接字抓包的实现原理依赖于Windows系统中提供的ioctlsocket函数,该函数可将指定的网卡设置为混杂模式,网卡混杂模式(Promiscuous Mode)是常用于计算机网络抓包的一种模式,也称为监听模式。在混杂模式下,网卡可以收到经过主机的所有数据包,而非只接收它所对应的MAC地址的数据包。

一般情况下,网卡会根据MAC地址过滤数据包,只有MAC地址与网卡所对应的设备的通信数据包才会被接收和处理,其他数据包则会被忽略。但在混杂模式下,网卡会接收经过它所连接的网络中所有的数据包,这些数据包可以是面向其他设备的通信数据包、广播数据包或多播数据包等。

混杂模式可以通过软件驱动程序或网卡硬件实现。启用混杂模式的主要用途之一是网络抓包分析,使用混杂模式可以捕获网络中所有的数据包,且不仅仅是它所连接的设备的通信数据包。因此,可以完整获取网络中的通信内容,便于进行网络监控、安全风险感知、漏洞检测等操作。

Windows系统下,开启混杂模式可以使用ioctlsocket()函数,该函数原型定义如下:

int ioctlsocket (
   SOCKET s,        //要操作的套接字
   long cmd,        //操作代码
   u_long *argp     //指向操作参数的指针
);

其中,参数说明如下:

  • s: 要执行I/O控制操作的套接字。
  • cmd: 操作代码,用于控制对套接字的特定操作。
  • argp: 与特定请求代码相关联的参数指针。此参数的具体含义取决于请求代码。

在该函数中,参数cmd指定了I/O控制操作代码,是一个整数值,用于控制对套接字的特定操作。argp是一个指向特定请求代码相关联的参数的指针,它的具体含义将取决于请求代码。函数返回值为int类型,表示函数执行结果的状态码,若函数执行成功,则其返回值为0,否则返回一个错误代码,并将错误原因存入errno变量中。

要实现抓包前提是需要先选中绑定到那个网卡,如下InitAndSelectNetworkRawSocket函数则是实现绑定套接字到特定网卡的实现流程,在代码中首先初始化并使用gethostname函数获取到当前主机的主机名,主机IP地址等基本信息,接着通过循环的方式将自身网卡信息追加到g_HostIp全局结构体内进行存储,通过使用一个交互式选择菜单让用户可以选中需要绑定的网卡名称,当用户选中后则下一步是绑定套接字,并通过调用ioctlsocket函数将网卡设置为混杂模式,至此网卡的绑定工作就算结束了,当读者需要操作时只需要对全局变量进行操作即可,而选择函数仅仅只是获取到网卡信息而已并没有实际的作用。

#include <iostream>
#include <WinSock2.h>
#include <ws2tcpip.h>
#include <mstcpip.h>

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

// 全局结构
typedef struct
{
  int iLen;
  char szIPArray[10][50];
}HOSTIP;

// 全局变量
SOCKET g_RawSocket = 0;
HOSTIP g_HostIp;

// -------------------------------------------------------
// 初始化与选择套接字
// -------------------------------------------------------
BOOL InitAndSelectNetworkRawSocket()
{
  // 设置套接字版本
  WSADATA wsaData = { 0 };
  if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
  {
    return FALSE;
  }
  // 创建原始套接字
  // Windows无法抓取RawSocket MAC层的数据包,只能抓到IP层及以上的数据包
  g_RawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
  // g_RawSocket = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
  if (INVALID_SOCKET == g_RawSocket)
  {
    WSACleanup();
    return FALSE;
  }

  // 绑定到接口 获取本机名
  char szHostName[MAX_PATH] = { 0 };
  if (SOCKET_ERROR == ::gethostname(szHostName, MAX_PATH))
  {
    closesocket(g_RawSocket);
    WSACleanup();
    return FALSE;
  }

  // 根据本机名获取本机IP地址
  hostent* lpHostent = ::gethostbyname(szHostName);
  if (NULL == lpHostent)
  {
    closesocket(g_RawSocket);
    WSACleanup();
    return FALSE;
  }

  // IP地址转换并保存IP地址
  g_HostIp.iLen = 0;
  strcpy(g_HostIp.szIPArray[g_HostIp.iLen], "127.0.0.1");
  g_HostIp.iLen++;
  char* lpszHostIP = NULL;

  while (NULL != (lpHostent->h_addr_list[(g_HostIp.iLen - 1)]))
  {
    lpszHostIP = inet_ntoa(*(in_addr*)lpHostent->h_addr_list[(g_HostIp.iLen - 1)]);
    strcpy(g_HostIp.szIPArray[g_HostIp.iLen], lpszHostIP);
    g_HostIp.iLen++;
  }

  // 选择IP地址对应的网卡来嗅探
  printf("选择侦听网卡 \n\n");
  for (int i = 0; i < g_HostIp.iLen; i++)
  {
    printf("\t [*] 序号: %d \t IP地址: %s \n", i, g_HostIp.szIPArray[i]);
  }

  printf("\n 选择网卡序号: ");
  int iChoose = 0;
  scanf("%d", &iChoose);

  // 如果选择超出范围则直接终止
  if ((0 > iChoose) || (iChoose >= g_HostIp.iLen))
  {
    exit(0);
  }
  if ((0 <= iChoose) && (iChoose < g_HostIp.iLen))
  {
    lpszHostIP = g_HostIp.szIPArray[iChoose];
  }

  // 构造地址结构
  sockaddr_in SockAddr = { 0 };
  RtlZeroMemory(&SockAddr, sizeof(sockaddr_in));
  SockAddr.sin_addr.S_un.S_addr = inet_addr(lpszHostIP);
  SockAddr.sin_family = AF_INET;
  SockAddr.sin_port = htons(0);

  // 绑定套接字
  if (SOCKET_ERROR == bind(g_RawSocket, (sockaddr*)(&SockAddr), sizeof(sockaddr_in)))
  {
    closesocket(g_RawSocket);
    WSACleanup();
    return FALSE;
  }

  // 设置混杂模式 抓取所有经过网卡的数据包
  DWORD dwSetVal = 1;
  if (SOCKET_ERROR == ioctlsocket(g_RawSocket, SIO_RCVALL, &dwSetVal))
  {
    closesocket(g_RawSocket);
    WSACleanup();
    return FALSE;
  }
  return TRUE;
}

int main(int argc, char *argv[])
{
  // 选择网卡并设置网络为非阻塞模式
  BOOL SelectFlag = InitAndSelectNetworkRawSocket();
  if (SelectFlag == TRUE)
  {
    printf("[*] 网卡已被选中 套接字ID = %d | 套接字IP = %s \n", g_RawSocket,g_HostIp.szIPArray);
  }

  system("pause");
  return 0;
}

读者可自行编译并以管理员身份运行上述代码片段,当读者运行后会看到如下图所示的代码片段,此处笔者就选择三号网卡进行绑定操作,当绑定后此时套接字ID对应的则是特定的网卡,后续的操作均可针对此套接字ID进行,如下图所示;

18.1 Socket 原生套接字抓包

当读者有了设置混杂模式的功能则下一步就是抓包了,抓包的实现很简单,只需要在开启了非阻塞混杂模式的网卡上使用recvfrom函数循环进行监听即可,当有数据包产生时则直接输出iRecvBytes中所存储的数据即可,这段代码的实现如下所示;

int main(int argc, char *argv[])
{
  // 选择网卡并设置网络为非阻塞模式
  BOOL init_flag = InitAndSelectNetworkRawSocket();
  if (init_flag == FALSE)
  {
    return 0;
  }

  sockaddr_in RecvAddr = { 0 };
  int iRecvBytes = 0;
  int iRecvAddrLen = sizeof(sockaddr_in);

  // 定义缓冲区长度
  DWORD dwBufSize = 12000;
  BYTE* lpRecvBuf = new BYTE[dwBufSize];

  // 循环接收接收
  while (1)
  {
    RtlZeroMemory(&RecvAddr, iRecvAddrLen);
    iRecvBytes = recvfrom(g_RawSocket, (char*)lpRecvBuf, dwBufSize, 0, (sockaddr*)(&RecvAddr), &iRecvAddrLen);
    if (0 < iRecvBytes)
    {
      // 接收数据包并输出
      printf("[接收数据包] %s \n", lpRecvBuf);
    }
  }

  // 释放内存
  delete[]lpRecvBuf;
  lpRecvBuf = NULL;

  // 关闭套接字
  Sleep(500);
  closesocket(g_RawSocket);
  WSACleanup();
  return 0;
}

当读者选择网卡后即可看到如下所示的输出结果,这些数据则是经过网卡192.168.9.125的所有数据,由于此处没有解码和区分数据包类型所以显示出的字符串并没有任何意义,如下图所示;

18.1 Socket 原生套接字抓包

接下来我们就需要根据不同的数据包类型对这些数据进行解包操作,在解包之前我们需要先来定义几个关键的数据包结构体,如下代码中ether_header代表的是以太网包头结构,该结构占用14个字节的存储空间,arp_header则是ARP结构体,该结构体占用28个字节,ARK结构中还存在一个ARK报文结构,该结构占用42字节的内存长度,接着分别顶一个ipv4_headeripv6_headertcp_headerudp_header等结构体,这些结构体的完整定义如下所示;

#pragma pack(1)

/*以太网帧头格式结构体 14个字节*/
typedef struct ether_header
{
    unsigned char ether_dhost[6];  // 目的MAC地址
    unsigned char ether_shost[6];  // 源MAC地址
    unsigned short ether_type;     // eh_type 的值需要考察上一层的协议,如果为ip则为0x0800
}ETHERHEADER, * PETHERHEADER;

/*以ARP字段结构体 28个字节*/
typedef struct arp_header
{
    unsigned short arp_hrd;
    unsigned short arp_pro;
    unsigned char arp_hln;
    unsigned char arp_pln;
    unsigned short arp_op;
    unsigned char arp_sourha[6];
    unsigned long arp_sourpa;
    unsigned char arp_destha[6];
    unsigned long arp_destpa;
}ARPHEADER, * PARPHEADER;

/*ARP报文结构体 42个字节*/
typedef struct arp_packet
{
    ETHERHEADER etherHeader;
    ARPHEADER   arpHeader;
}ARPPACKET, * PARPPACKET;

/*IPv4报头结构体 20个字节*/
typedef struct ipv4_header
{
    unsigned char ipv4_ver_hl;        // Version(4 bits) + Internet Header Length(4 bits)长度按4字节对齐
    unsigned char ipv4_stype;         // 服务类型
    unsigned short ipv4_plen;         // 总长度(包含IP数据头,TCP数据头以及数据)
    unsigned short ipv4_pidentify;    // ID定义单独IP
    unsigned short ipv4_flag_offset;  // 标志位偏移量
    unsigned char ipv4_ttl;           // 生存时间
    unsigned char ipv4_pro;           // 协议类型
    unsigned short ipv4_crc;          // 校验和
    unsigned long  ipv4_sourpa;       // 源IP地址
    unsigned long  ipv4_destpa;       // 目的IP地址
}IPV4HEADER, * PIPV4HEADER;

/*IPv6报头结构体 40个字节*/
typedef struct ipv6_header
{
    unsigned char ipv6_ver_hl;
    unsigned char ipv6_priority;
    unsigned short ipv6_lable;
    unsigned short ipv6_plen;
    unsigned char  ipv6_nextheader;
    unsigned char  ipv6_limits;
    unsigned char ipv6_sourpa[16];
    unsigned char ipv6_destpa[16];
}IPV6HEADER, * PIPV6HEADER;

/*TCP报头结构体 20个字节*/
typedef struct tcp_header
{
    unsigned short tcp_sourport;     // 源端口
    unsigned short tcp_destport;     // 目的端口
    unsigned long  tcp_seqnu;        // 序列号
    unsigned long  tcp_acknu;        // 确认号
    unsigned char  tcp_hlen;         // 4位首部长度
    unsigned char  tcp_reserved;     // 标志位
    unsigned short tcp_window;       // 窗口大小
    unsigned short tcp_chksum;       // 检验和
    unsigned short tcp_urgpoint;     // 紧急指针
}TCPHEADER, * PTCPHEADER;

/*UDP报头结构体 8个字节*/
typedef struct udp_header
{
    unsigned short udp_sourport;   // 源端口 
    unsigned short udp_destport;   // 目的端口
    unsigned short udp_hlen;       // 长度
    unsigned short udp_crc;        // 校验和
}UDPHEADER, * PUDPHEADER;
#pragma pack()

当有了结构体的定义部分,则实现对数据包的解析只需要判断数据包的类型并使用不同的结构体对数据包进行解包打印即可,如下是实现数据包解析的完整代码,在代码中分别实现了几个核心函数,其中printData函数可以实现对特定内存数据的十六进制格式输出方便检查输出效果,函数AnalyseRecvPacket_All用于解析除去TCP/UDP格式的其他数据包,AnalyseRecvPacket_TCP用于解析TCP数据,AnalyseRecvPacket_UDP用于解析UDP数据,在主函数中通过使用ip->ipv4_pro判断数据包的具体类型,并根据类型的不同依次调用不同的函数实现数据包解析。

// 输出数据包
void PrintData(BYTE* lpBuf, int iLen, int iPrintType)
{
  // 16进制
  if (0 == iPrintType)
  {
    for (int i = 0; i < iLen; i++)
    {
      if ((0 == (i % 8)) && (0 != i))
      {
        printf("  ");
      }
      if ((0 == (i % 16)) && (0 != i))
      {
        printf("\n");
      }
      printf("%02x ", lpBuf[i]);

    }
    printf("\n");
  }
  // ASCII编码
  else if (1 == iPrintType)
  {
    for (int i = 0; i < iLen; i++)
    {
      printf("%c", lpBuf[i]);
    }
    printf("\n");
  }
}

// 解析所有其他数据包
void AnalyseRecvPacket_All(BYTE* lpBuf)
{
  struct sockaddr_in saddr, daddr;
  PIPV4HEADER ip = (PIPV4HEADER)lpBuf;
  saddr.sin_addr.s_addr = ip->ipv4_sourpa;
  daddr.sin_addr.s_addr = ip->ipv4_destpa;

  printf("From:%s --> ", inet_ntoa(saddr.sin_addr));
  printf("To:%s\n", inet_ntoa(daddr.sin_addr));
}

// 解析TCP数据包
void AnalyseRecvPacket_TCP(BYTE* lpBuf)
{
  struct sockaddr_in saddr, daddr;
  PIPV4HEADER ip = (PIPV4HEADER)lpBuf;
  PTCPHEADER tcp = (PTCPHEADER)(lpBuf + (ip->ipv4_ver_hl & 0x0F) * 4);
  int hlen = (ip->ipv4_ver_hl & 0x0F) * 4 + tcp->tcp_hlen * 4;

  // 这里要将网络字节序转换为本地字节序
  int dlen = ntohs(ip->ipv4_plen) - hlen;
  saddr.sin_addr.s_addr = ip->ipv4_sourpa;
  daddr.sin_addr.s_addr = ip->ipv4_destpa;

  printf("From:%s:%d --> ", inet_ntoa(saddr.sin_addr), ntohs(tcp->tcp_sourport));
  printf("To:%s:%d  ", inet_ntoa(daddr.sin_addr), ntohs(tcp->tcp_destport));
  printf("ack:%u  syn:%u length=%d\n", tcp->tcp_acknu, tcp->tcp_seqnu, dlen);

  PrintData((lpBuf + hlen), dlen, 0);
}

// 解析UDP数据包
void AnalyseRecvPacket_UDP(BYTE* lpBuf)
{
  struct sockaddr_in saddr, daddr;
  PIPV4HEADER ip = (PIPV4HEADER)lpBuf;
  PUDPHEADER udp = (PUDPHEADER)(lpBuf + (ip->ipv4_ver_hl & 0x0F) * 4);
  int hlen = (int)((ip->ipv4_ver_hl & 0x0F) * 4 + sizeof(UDPHEADER));
  int dlen = (int)(ntohs(udp->udp_hlen) - 8);

  //  int dlen = (int)(udp->udp_hlen - 8);
  saddr.sin_addr.s_addr = ip->ipv4_sourpa;
  daddr.sin_addr.s_addr = ip->ipv4_destpa;
  printf("Protocol:UDP  ");
  printf("From:%s:%d -->", inet_ntoa(saddr.sin_addr), ntohs(udp->udp_sourport));
  printf("To:%s:%d\n", inet_ntoa(daddr.sin_addr), ntohs(udp->udp_destport));

  PrintData((lpBuf + hlen), dlen, 0);
}

int main(int argc, char* argv[])
{
  // 选择网卡,并设置网络为非阻塞模式
  InitAndSelectNetworkRawSocket();

  sockaddr_in RecvAddr = { 0 };
  int iRecvBytes = 0;
  int iRecvAddrLen = sizeof(sockaddr_in);
  DWORD dwBufSize = 12000;
  BYTE* lpRecvBuf = new BYTE[dwBufSize];

  // 循环接收接收
  while (1)
  {
    RtlZeroMemory(&RecvAddr, iRecvAddrLen);
    iRecvBytes = recvfrom(g_RawSocket, (char*)lpRecvBuf, dwBufSize, 0, (sockaddr*)(&RecvAddr), &iRecvAddrLen);
    if (0 < iRecvBytes)
    {
      // 接收数据包解码输出
      // 分析IP包的协议类型
      PIPV4HEADER ip = (PIPV4HEADER)lpRecvBuf;
      switch (ip->ipv4_pro)
      {
      case IPPROTO_ICMP:
      {
                // 分析ICMP
        printf("[ICMP]\n");
        AnalyseRecvPacket_All(lpRecvBuf);
        break;
      }
      case IPPROTO_IGMP:
      {
                // 分析IGMP
        printf("[IGMP]\n");
        AnalyseRecvPacket_All(lpRecvBuf);
        break;
      }
      case IPPROTO_TCP:
      {
        // 分析tcp协议
        printf("[TCP]\n");
        AnalyseRecvPacket_TCP(lpRecvBuf);
        break;
      }
      case IPPROTO_UDP:
      {
        // 分析udp协议
        printf("[UDP]\n");
        AnalyseRecvPacket_UDP(lpRecvBuf);
        break;
      }
      default:
      {
                // 其他数据包
        printf("[OTHER IP]\n");
        AnalyseRecvPacket_All(lpRecvBuf);
        break;
      }
      }
    }
  }

  // 释放内存
  delete[]lpRecvBuf;
  lpRecvBuf = NULL;

  // 关闭套接字
  Sleep(500);
  closesocket(g_RawSocket);
  WSACleanup();
  return 0;
}

读者可自行编译并运行上述代码片段,当程序运行后可自行选择希望监控的网卡,当程序中检测到TCP数据包后会输出如下图所示的提示信息,在图中我们可以清晰的看出数据包的流向信息,以及数据包长度数据包内的数据等;

18.1 Socket 原生套接字抓包

当读者通过使用Ping命令探测目标主机时,此时同样可以抓取到ICMP相关的数据流,只是在数据解析时并没有太规范导致只能看到简单的流向,当然读者也可以自行完善这段代码,让其能够解析更多参数。

18.1 Socket 原生套接字抓包文章来源地址https://www.toymoban.com/news/detail-711053.html

到了这里,关于18.1 Socket 原生套接字抓包的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 网络编程之 Socket 套接字(使用数据报套接字和流套接字分别实现一个小程序(附源码))

    网络编程是指网络上的主机,通过不同的进程,以编程的方式实现 网络通信(或称为网络数据传输) 只要满足不同的进程就可以进行通信,所以即便是在同一个主机,只要不同的进程,基于网络传输数据,也属于网络编程 在一次网络传输中: 发送端: 数据的 发送方进程

    2024年02月03日
    浏览(63)
  • 【网络协议】聊聊套接字socket

    网络编程我们知道是通过socket进行编程的,其实socket也是基于TCP和UDP协议进行编程的。但是在socket层面是感知不到下层的,所以在设置参数的时候,其实是端到端协议智商的网络层和传输层。TCP是数据流所以设置为SOCK_STREAM,而UDP是基于数据报的,设置为SOCK_DGRAM 整体流程其实

    2024年02月07日
    浏览(43)
  • 网络编程套接字(Socket)

    认识IP地址, 端口号, 网络字节序等网络编程中的基本概念; 学习socket api的基本用法; 能够实现一个简单的udp客户端/服务器; 能够实现一个简单的tcp客户端/服务器(单连接版本, 多进程版本, 多线程版本); 理解tcp服务器建立连接, 发送数据, 断开连接的流程; 通俗易懂地说,源

    2024年01月21日
    浏览(58)
  • 【网络编程】socket套接字

    如果我们的台式机或者笔记本没有IP地址就无法上网,而因为每台主机都有IP地址,所以注定了数据从一台主机传输到另一台主机 一定有源IP和目的IP 。 所以在报头中就会包含源IP和目的IP。 而我们把数据从一台主机传递到另一台主机并不是目的,真正通信的其实是应用层上的

    2024年02月02日
    浏览(66)
  • python--socket(套接字/插口)

    ** 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机之间的进程通信,我们网络上各种各样的服务大多都是基于socket来完成通信的,例如我们浏览网页,qq聊天、收发emil;** Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口

    2024年02月14日
    浏览(36)
  • 14.1 Socket 套接字编程入门

    Winsock是Windows操作系统上的套接字API,用于在网络上进行数据通信。套接字通信是一种允许应用程序在计算机网络上进行实时数据交换的技术。通过使用Windows提供的API,应用程序可以创建一个套接字来进行数据通信。这个套接字可以绑定到一个端口,以允许其他应用程序连接

    2024年02月08日
    浏览(47)
  • 【网络】socket套接字基础知识

    IP 每台主机都有自己的IP地址,所以当数据从一台主机传输到另一台主机就需要IP地址。报头中就会包含源IP和目的IP 源IP地址:发送数据报那个主机的IP地址,目的IP地址:想发送到的那个主机的IP地址 我们把数据从一台主机传递到另一台主机不是真正目的,真正通信的不是这

    2024年02月03日
    浏览(44)
  • 网络编程—Socket套接字详解

    目录 一、网络编程 1.1、为什么需要网络编程? 1.2、什么是网络编程 1.3、发送端和接收端 ​编辑1.4、请求和响应 ​编辑1.5、客户端和服务端  二、Socket套接字  2.1、概念 2.2、分类  2.2.1、流套接字  2.2.2、数据报套接字  2.2.3、原始套接字  2.3、Socket编程注意事项  1.1、为什

    2024年02月16日
    浏览(54)
  • 14.10 Socket 套接字选择通信

    对于网络通信中的服务端来说,显然不可能是一对一的,我们所希望的是服务端启用一份则可以选择性的与特定一个客户端通信,而当不需要与客户端通信时,则只需要将该套接字挂到链表中存储并等待后续操作,套接字服务端通过多线程实现存储套接字和选择通信,可以提

    2024年02月08日
    浏览(48)
  • 【IPC通信--socket套接字--心跳包】

    随着网络通信技术的不断发展,网络通信已成为我们日常工作和生活中不可或缺的一部分。但是在使用网络通信的过程中,时常会遇到网络延迟、丢包等问题,这些问题不仅影响我们的工作和生活效率,也会给我们的网络带来一定的风险和安全隐患。为了解决这些问题,Soc

    2024年01月22日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包