• TCP/IP 网络嗅探器开发实例


    主要内容

    实例使用环境

    知识储备

    IP数据报格式

    IP头结构体定义

    TCP头格式

    TCP头结构体定义

    实例的调用演示

     实例的完整代码

    initsock.h

    protoinfo.h文件 

     Sniffer.cpp文件

    实例总结

    基于原始套接字的网络封包嗅探的工作过程

    Sniffer节点调用分析

    在Visual Studio2019 的调用事项

    问题一:程序的打开问题

    问题二:解决编译器的C4996代码问题


    主要内容

    1,了解利用原始套接字进行通信程序的编写,编译和执行。

    2,利用原始套接字编程将网卡设置为混杂模式,对局域网(自组网/机房网络/宿舍网络或其他)上传输的数据包进行捕获与分析;

    3,能分析出捕获的IP数据包的生存时间、协议、头部校验和、源IP地址以及目标IP地址等内容。并将上述解析结果规范显示出来。

    实例使用环境

    操作系统:  Microsoft Windows 11

    编程环境:  Visual Studio 2019


    知识储备

    IP数据报格式

    IP数据报格式

    IP头结构体定义

    1. IP头结构体
    2. typedef struct _IPHeader // 20字节的IP头
    3. {
    4. UCHAR iphVerLen; // 4位首部长度+4位IP版本号
    5. UCHAR ipTOS; // 8位服务类型
    6. USHORT ipLength; // 16位封包总长度,即整个IP报的长度
    7. USHORT ipID; // 16位封包标识,惟一标识发送的每一个数据报
    8. USHORT ipFlags; // 3位标志位+13报片偏移
    9. UCHAR ipTTL; // 8位生存时间,就是TTL
    10. UCHAR ipProtocol; // 8位协议,TCP、UDP、ICMP等
    11. USHORT ipChecksum; // 16位IP首部校验和
    12. ULONG ipSource; // 32位源IP地址
    13. ULONG ipDestination; // 32位目标IP地址
    14. } IPHeader, * PIPHeader;

    TCP头格式

    TCP包头格式

    TCP头结构体定义

    1. typedef struct _TCPHeader // 20字节的TCP头
    2. {
    3. USHORT sourcePort; // 16位源端口号
    4. USHORT destinationPort; // 16位目的端口号
    5. ULONG sequenceNumber; // 32位序列号
    6. ULONG acknowledgeNumber; // 32位确认号
    7. UCHAR dataoffset; // 高4位表示数据偏移,低6位保留字
    8. UCHAR flags; // 6位标志位
    9. USHORT windows; // 16位窗口大小
    10. USHORT checksum; // 16位校验和
    11. USHORT urgentPointer; // 16位紧急数据偏移量
    12. } TCPHeader, * PTCPHeader;


    实例的调用演示

    程序成功绑定了本机的IP地址(注意不能是任意)

    程序尝试嗅探主机中的IP数据封包,并成功解析出源IP地址以及目标IP地址,协议及对应端口等内容。


     实例的完整代码

    initsock.h

    1. #include
    2. #pragma comment(lib, "WS2_32") // 链接到WS2_32.lib
    3. class CInitSock
    4. {
    5. public:
    6. CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
    7. {
    8. // 初始化WS2_32.dll
    9. WSADATA wsaData;
    10. WORD sockVersion = MAKEWORD(minorVer, majorVer);
    11. if (::WSAStartup(sockVersion, &wsaData) != 0)
    12. {
    13. exit(0);
    14. }
    15. }
    16. ~CInitSock()
    17. {
    18. ::WSACleanup();
    19. }
    20. };

    protoinfo.h文件 

    1. // protoinfo.h文件
    2. /*
    3. 定义协议格式
    4. 定义协议中使用的宏
    5. */
    6. #ifndef __PROTOINFO_H__
    7. #define __PROTOINFO_H__
    8. #define ETHERTYPE_IP 0x0800
    9. #define ETHERTYPE_ARP 0x0806
    10. typedef struct _ETHeader // 14字节的以太头
    11. {
    12. UCHAR dhost[6]; // 目的MAC地址destination mac address
    13. UCHAR shost[6]; // 源MAC地址source mac address
    14. USHORT type; // 下层协议类型,如IP(ETHERTYPE_IP)、ARP(ETHERTYPE_ARP)等
    15. } ETHeader, * PETHeader;
    16. #define ARPHRD_ETHER 1
    17. // ARP协议opcodes
    18. #define ARPOP_REQUEST 1 // ARP 请求
    19. #define ARPOP_REPLY 2 // ARP 响应
    20. typedef struct _ARPHeader // 28字节的ARP头
    21. {
    22. USHORT hrd; // 硬件地址空间,以太网中为ARPHRD_ETHER
    23. USHORT eth_type; // 以太网类型,ETHERTYPE_IP ??
    24. UCHAR maclen; // MAC地址的长度,为6
    25. UCHAR iplen; // IP地址的长度,为4
    26. USHORT opcode; // 操作代码,ARPOP_REQUEST为请求,ARPOP_REPLY为响应
    27. UCHAR smac[6]; // 源MAC地址
    28. UCHAR saddr[4]; // 源IP地址
    29. UCHAR dmac[6]; // 目的MAC地址
    30. UCHAR daddr[4]; // 目的IP地址
    31. } ARPHeader, * PARPHeader;
    32. // 协议
    33. #define PROTO_ICMP 1
    34. #define PROTO_IGMP 2
    35. #define PROTO_TCP 6
    36. #define PROTO_UDP 17
    37. typedef struct _IPHeader // 20字节的IP头
    38. {
    39. UCHAR iphVerLen; // 版本号和头长度(各占4位)
    40. UCHAR ipTOS; // 服务类型
    41. USHORT ipLength; // 封包总长度,即整个IP报的长度
    42. USHORT ipID; // 封包标识,惟一标识发送的每一个数据报
    43. USHORT ipFlags; // 标志
    44. UCHAR ipTTL; // 生存时间,就是TTL
    45. UCHAR ipProtocol; // 协议,可能是TCP、UDP、ICMP等
    46. USHORT ipChecksum; // 校验和
    47. ULONG ipSource; // 源IP地址
    48. ULONG ipDestination; // 目标IP地址
    49. } IPHeader, * PIPHeader;
    50. // 定义TCP标志
    51. #define TCP_FIN 0x01
    52. #define TCP_SYN 0x02
    53. #define TCP_RST 0x04
    54. #define TCP_PSH 0x08
    55. #define TCP_ACK 0x10
    56. #define TCP_URG 0x20
    57. #define TCP_ACE 0x40
    58. #define TCP_CWR 0x80
    59. typedef struct _TCPHeader // 20字节的TCP头
    60. {
    61. USHORT sourcePort; // 16位源端口号
    62. USHORT destinationPort; // 16位目的端口号
    63. ULONG sequenceNumber; // 32位序列号
    64. ULONG acknowledgeNumber; // 32位确认号
    65. UCHAR dataoffset; // 高4位表示数据偏移
    66. UCHAR flags; // 6位标志位
    67. //FIN - 0x01
    68. //SYN - 0x02
    69. //RST - 0x04
    70. //PUSH- 0x08
    71. //ACK- 0x10
    72. //URG- 0x20
    73. //ACE- 0x40
    74. //CWR- 0x80
    75. USHORT windows; // 16位窗口大小
    76. USHORT checksum; // 16位校验和
    77. USHORT urgentPointer; // 16位紧急数据偏移量
    78. } TCPHeader, * PTCPHeader;
    79. typedef struct _UDPHeader
    80. {
    81. USHORT sourcePort; // 源端口号
    82. USHORT destinationPort;// 目的端口号
    83. USHORT len; // 封包长度
    84. USHORT checksum; // 校验和
    85. } UDPHeader, * PUDPHeader;
    86. #endif // __PROTOINFO_H__

     Sniffer.cpp文件

    1. // Sniffer.cpp文件
    2. #pragma warning(disable:4996)
    3. #include "initsock.h"
    4. #include "protoinfo.h"
    5. #include
    6. #include
    7. #pragma comment(lib, "Advapi32.lib")
    8. CInitSock theSock;
    9. void DecodeTCPPacket(char* pData)
    10. {
    11. TCPHeader* pTCPHdr = (TCPHeader*)pData;
    12. printf(" Port: %d -> %d \n", ntohs(pTCPHdr->sourcePort), ntohs(pTCPHdr->destinationPort));
    13. // 下面还可以根据目的端口号进一步解析应用层协议
    14. switch (::ntohs(pTCPHdr->destinationPort))
    15. {
    16. case 21:
    17. break;
    18. case 80:
    19. case 8080:
    20. break;
    21. }
    22. }
    23. void DecodeIPPacket(char* pData)
    24. {
    25. IPHeader* pIPHdr = (IPHeader*)pData;
    26. in_addr source, dest;
    27. char szSourceIp[32], szDestIp[32];
    28. printf("\n\n-------------------------------\n");
    29. // 从IP头中取出源IP地址和目的IP地址
    30. source.S_un.S_addr = pIPHdr->ipSource;
    31. dest.S_un.S_addr = pIPHdr->ipDestination;
    32. strcpy(szSourceIp, ::inet_ntoa(source));
    33. strcpy(szDestIp, ::inet_ntoa(dest));
    34. printf(" %s -> %s \n", szSourceIp, szDestIp);
    35. // IP头长度
    36. int nHeaderLen = (pIPHdr->iphVerLen & 0xf) * sizeof(ULONG);
    37. switch (pIPHdr->ipProtocol)
    38. {
    39. case IPPROTO_TCP: // TCP协议
    40. DecodeTCPPacket(pData + nHeaderLen);
    41. break;
    42. case IPPROTO_UDP:
    43. break;
    44. case IPPROTO_ICMP:
    45. break;
    46. }
    47. }
    48. void main()
    49. {
    50. // 创建原始套节字
    51. SOCKET sRaw = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
    52. // 获取本地IP地址
    53. char szHostName[56];
    54. SOCKADDR_IN addr_in;
    55. struct hostent* pHost;
    56. gethostname(szHostName, 56);
    57. if ((pHost = gethostbyname((char*)szHostName)) == NULL)
    58. return;
    59. // 在调用ioctl之前,套节字必须绑定
    60. addr_in.sin_family = AF_INET;
    61. addr_in.sin_port = htons(0);
    62. memcpy(&addr_in.sin_addr.S_un.S_addr, pHost->h_addr_list[0], pHost->h_length);
    63. printf(" Binding to interface : %s \n", ::inet_ntoa(addr_in.sin_addr));
    64. if (bind(sRaw, (PSOCKADDR)&addr_in, sizeof(addr_in)) == SOCKET_ERROR)
    65. return;
    66. // 设置SIO_RCVALL控制代码,以便接收所有的IP包
    67. DWORD dwValue = 1;
    68. if (ioctlsocket(sRaw, SIO_RCVALL, &dwValue) != 0)
    69. return;
    70. // 开始接收封包
    71. char buff[1024];
    72. int nRet;
    73. while (TRUE)
    74. {
    75. nRet = recv(sRaw, buff, 1024, 0);
    76. if (nRet > 0)
    77. {
    78. DecodeIPPacket(buff);
    79. }
    80. }
    81. closesocket(sRaw);
    82. }

    实例总结

    基于原始套接字的网络封包嗅探工作过程

    使用原始套接字写嗅探器的流程:

    1 使用socket创建基于IP协议的原始套接字。

    2 获取本地IP地址。

    3 将原始套接字绑定到本地IP地址上。

    4 使用ioctlsocket函数设置套接字选项SIO_RCVALL,即接受所有数据。

    5 无尽调用recv函数。

    Sniffer节点调用分析

        通常的套接字程序只能响应与自己MAC地址相匹配的或是以广播形式发出的数据帧,对于其他形式的数据帧网络接口采取的动作是直接丢弃。为了使网卡接收所有经过它的封包,要将其设置为混杂模式。

        程序运行之后,首先要创建原始套接字,将它绑定到一个明确的本地地址(不能为ADDR_ANY),然后设置SIO_RCVALL控制代码,最后进入无限循环,不断调用recv函数接收经过本地网卡的IP封包。

        程序接收到IP封包之后,调用自定义函数DecodeIPPaccket进行解包。这个函数萃取出封包中的协议头,向用户打印出协议信息,本次实验Sniffer程序仅解析了封包中的IP头和TCP头

    在Visual Studio2019 的调用事项

    问题一:程序的打开问题

    由于网络嗅探器程序需要较高的使用权限,所以要以编译器要以管理员的身份打开使用。

    问题二:解决编译器的C4996代码问题

    由于代码样例的使用规则与现行的编译器的代码定义规则(安全性过高)存在不兼容的问题,在尝试过许多方法后,尝试解决思路如下:

    如果都没很好的解决问题,最后只能输入#pragma warning(disable:4996)强制忽略掉C4996所带来的警告程序恢复了正常运行。

  • 相关阅读:
    【前端进阶——ES6基础①】ES6究竟有什么不同呢!
    如何高性能、高效率地实现3D Web轻量化?
    声网赵斌:RTE 体验提升,新一代 Killer App 将成为现实丨RTE 2022
    计算机毕业设计 基于SSM的电影推荐系统的设计与实现 Java实战项目 附源码+文档+视频讲解
    DeeplabV3实战:基于tensorflow搭建DeeplabV3实现语义分割任务
    简单工厂模式
    抖音矩阵系统,抖音矩阵源码定制。
    .NET Var与Dynamic
    3万字《SpringBoot微服务开发——Shiro(安全)》
    算法设计与分析-0/1背包问题
  • 原文地址:https://blog.csdn.net/weixin_51989356/article/details/128169733