1,了解利用原始套接字进行通信程序的编写,编译和执行。
2,利用原始套接字编程将网卡设置为混杂模式,对局域网(自组网/机房网络/宿舍网络或其他)上传输的数据包进行捕获与分析;
3,能分析出捕获的IP数据包的生存时间、协议、头部校验和、源IP地址以及目标IP地址等内容。并将上述解析结果规范显示出来。
操作系统: Microsoft Windows 11
编程环境: Visual Studio 2019
- IP头结构体
- typedef struct _IPHeader // 20字节的IP头
- {
- UCHAR iphVerLen; // 4位首部长度+4位IP版本号
- UCHAR ipTOS; // 8位服务类型
- USHORT ipLength; // 16位封包总长度,即整个IP报的长度
- USHORT ipID; // 16位封包标识,惟一标识发送的每一个数据报
- USHORT ipFlags; // 3位标志位+13报片偏移
- UCHAR ipTTL; // 8位生存时间,就是TTL
- UCHAR ipProtocol; // 8位协议,TCP、UDP、ICMP等
- USHORT ipChecksum; // 16位IP首部校验和
- ULONG ipSource; // 32位源IP地址
- ULONG ipDestination; // 32位目标IP地址
- } IPHeader, * PIPHeader;
- typedef struct _TCPHeader // 20字节的TCP头
- {
- USHORT sourcePort; // 16位源端口号
- USHORT destinationPort; // 16位目的端口号
- ULONG sequenceNumber; // 32位序列号
- ULONG acknowledgeNumber; // 32位确认号
- UCHAR dataoffset; // 高4位表示数据偏移,低6位保留字
- UCHAR flags; // 6位标志位
- USHORT windows; // 16位窗口大小
- USHORT checksum; // 16位校验和
- USHORT urgentPointer; // 16位紧急数据偏移量
- } TCPHeader, * PTCPHeader;
程序成功绑定了本机的IP地址(注意不能是任意)

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

- #include
- #pragma comment(lib, "WS2_32") // 链接到WS2_32.lib
-
- class CInitSock
- {
- public:
- CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
- {
- // 初始化WS2_32.dll
- WSADATA wsaData;
- WORD sockVersion = MAKEWORD(minorVer, majorVer);
- if (::WSAStartup(sockVersion, &wsaData) != 0)
- {
- exit(0);
- }
- }
- ~CInitSock()
- {
- ::WSACleanup();
- }
- };
- // protoinfo.h文件
-
- /*
- 定义协议格式
- 定义协议中使用的宏
- */
- #ifndef __PROTOINFO_H__
- #define __PROTOINFO_H__
-
-
- #define ETHERTYPE_IP 0x0800
- #define ETHERTYPE_ARP 0x0806
-
- typedef struct _ETHeader // 14字节的以太头
- {
- UCHAR dhost[6]; // 目的MAC地址destination mac address
- UCHAR shost[6]; // 源MAC地址source mac address
- USHORT type; // 下层协议类型,如IP(ETHERTYPE_IP)、ARP(ETHERTYPE_ARP)等
- } ETHeader, * PETHeader;
-
-
- #define ARPHRD_ETHER 1
-
- // ARP协议opcodes
- #define ARPOP_REQUEST 1 // ARP 请求
- #define ARPOP_REPLY 2 // ARP 响应
-
-
- typedef struct _ARPHeader // 28字节的ARP头
- {
- USHORT hrd; // 硬件地址空间,以太网中为ARPHRD_ETHER
- USHORT eth_type; // 以太网类型,ETHERTYPE_IP ??
- UCHAR maclen; // MAC地址的长度,为6
- UCHAR iplen; // IP地址的长度,为4
- USHORT opcode; // 操作代码,ARPOP_REQUEST为请求,ARPOP_REPLY为响应
- UCHAR smac[6]; // 源MAC地址
- UCHAR saddr[4]; // 源IP地址
- UCHAR dmac[6]; // 目的MAC地址
- UCHAR daddr[4]; // 目的IP地址
- } ARPHeader, * PARPHeader;
-
-
- // 协议
- #define PROTO_ICMP 1
- #define PROTO_IGMP 2
- #define PROTO_TCP 6
- #define PROTO_UDP 17
-
- 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;
-
-
- // 定义TCP标志
- #define TCP_FIN 0x01
- #define TCP_SYN 0x02
- #define TCP_RST 0x04
- #define TCP_PSH 0x08
- #define TCP_ACK 0x10
- #define TCP_URG 0x20
- #define TCP_ACE 0x40
- #define TCP_CWR 0x80
-
- typedef struct _TCPHeader // 20字节的TCP头
- {
- USHORT sourcePort; // 16位源端口号
- USHORT destinationPort; // 16位目的端口号
- ULONG sequenceNumber; // 32位序列号
- ULONG acknowledgeNumber; // 32位确认号
- UCHAR dataoffset; // 高4位表示数据偏移
- UCHAR flags; // 6位标志位
- //FIN - 0x01
- //SYN - 0x02
- //RST - 0x04
- //PUSH- 0x08
- //ACK- 0x10
- //URG- 0x20
- //ACE- 0x40
- //CWR- 0x80
-
- USHORT windows; // 16位窗口大小
- USHORT checksum; // 16位校验和
- USHORT urgentPointer; // 16位紧急数据偏移量
- } TCPHeader, * PTCPHeader;
-
- typedef struct _UDPHeader
- {
- USHORT sourcePort; // 源端口号
- USHORT destinationPort;// 目的端口号
- USHORT len; // 封包长度
- USHORT checksum; // 校验和
- } UDPHeader, * PUDPHeader;
-
- #endif // __PROTOINFO_H__
- // Sniffer.cpp文件
- #pragma warning(disable:4996)
- #include "initsock.h"
- #include "protoinfo.h"
-
- #include
- #include
-
- #pragma comment(lib, "Advapi32.lib")
-
- CInitSock theSock;
-
- void DecodeTCPPacket(char* pData)
- {
- TCPHeader* pTCPHdr = (TCPHeader*)pData;
-
- printf(" Port: %d -> %d \n", ntohs(pTCPHdr->sourcePort), ntohs(pTCPHdr->destinationPort));
-
- // 下面还可以根据目的端口号进一步解析应用层协议
- switch (::ntohs(pTCPHdr->destinationPort))
- {
- case 21:
- break;
- case 80:
- case 8080:
- break;
- }
- }
-
- void DecodeIPPacket(char* pData)
- {
- IPHeader* pIPHdr = (IPHeader*)pData;
- in_addr source, dest;
- char szSourceIp[32], szDestIp[32];
-
- printf("\n\n-------------------------------\n");
-
- // 从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));
-
- printf(" %s -> %s \n", szSourceIp, szDestIp);
- // IP头长度
- int nHeaderLen = (pIPHdr->iphVerLen & 0xf) * sizeof(ULONG);
-
- switch (pIPHdr->ipProtocol)
- {
- case IPPROTO_TCP: // TCP协议
- DecodeTCPPacket(pData + nHeaderLen);
- break;
- case IPPROTO_UDP:
- break;
- case IPPROTO_ICMP:
- break;
- }
- }
-
-
- void main()
- {
- // 创建原始套节字
- SOCKET sRaw = 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;
-
- // 在调用ioctl之前,套节字必须绑定
- 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);
-
- printf(" Binding to interface : %s \n", ::inet_ntoa(addr_in.sin_addr));
- if (bind(sRaw, (PSOCKADDR)&addr_in, sizeof(addr_in)) == SOCKET_ERROR)
- return;
-
- // 设置SIO_RCVALL控制代码,以便接收所有的IP包
- DWORD dwValue = 1;
- if (ioctlsocket(sRaw, SIO_RCVALL, &dwValue) != 0)
- return;
-
- // 开始接收封包
- char buff[1024];
- int nRet;
- while (TRUE)
- {
- nRet = recv(sRaw, buff, 1024, 0);
- if (nRet > 0)
- {
- DecodeIPPacket(buff);
- }
- }
- closesocket(sRaw);
- }
使用原始套接字写嗅探器的流程:
1 使用socket创建基于IP协议的原始套接字。
2 获取本地IP地址。
3 将原始套接字绑定到本地IP地址上。
4 使用ioctlsocket函数设置套接字选项SIO_RCVALL,即接受所有数据。
5 无尽调用recv函数。
通常的套接字程序只能响应与自己MAC地址相匹配的或是以广播形式发出的数据帧,对于其他形式的数据帧网络接口采取的动作是直接丢弃。为了使网卡接收所有经过它的封包,要将其设置为混杂模式。
程序运行之后,首先要创建原始套接字,将它绑定到一个明确的本地地址(不能为ADDR_ANY),然后设置SIO_RCVALL控制代码,最后进入无限循环,不断调用recv函数接收经过本地网卡的IP封包。
程序接收到IP封包之后,调用自定义函数DecodeIPPaccket进行解包。这个函数萃取出封包中的协议头,向用户打印出协议信息,本次实验Sniffer程序仅解析了封包中的IP头和TCP头。
由于网络嗅探器程序需要较高的使用权限,所以要以编译器要以管理员的身份打开使用。


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

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