网络嗅探器:把网卡设置成混杂模式,并可实现对网络上传输的数据包的捕获与分析。
原理:
通常的套接字程序只能响应与自己MAC地址相匹配的 或者是 广播形式发出的数据帧,对于其他形式的数据帧网络接口采取的动作是直接丢弃
为了使网卡接收所有经过他的封包,要将其设置成混杂模式,通过原始套接字来实现。
设置混杂模式:
创建原始套接字,
绑定到一个明确的本地地址,
向套接字发送SIO_RCVALL控制命令,
接收所有的IP包
代码实现步骤:
1 创建原始套接字
2 绑定到明确地址
3 这是SIO_RCVALL控制代码
4 进入循环,调用recv函数接收经过本地网卡的IP封包。
主程序代码如下:
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; //套接字绑定 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,(sockaddr*)&addr_in,sizeof(addr_in))==SOCKET_ERROR) return; //设置SIO_RECVALL控制代码 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); }
程序接收到IP封包后,调用自定义的DecodeIPPacket进行解包。取出封包中的协议头,向用户打印出协议信息。
解析IP头代码:
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: DecodeTCPPacket(pData+nHeaderLen); break; case IPPROTO_UDP: break; case IPPROTO_ICMP: break; } }
解析TCP头代码如下:取出端口号,输出
void DecodeTCPPacket(char *pData) { TCPHeader &pTCPHdr = (TCPHeader*)pData; printf("Port:%d->%d\n",ntohs(pTCPHdr->sourcePort),ntohs(pTCOHdr->destinationPort)); switch(::ntohs(pTCPHdr->destinationPort)) { case 21: break; case 80: break; case 8080: break; } }
VS下完整代码:
initsock.h:
#include <winsock2.h> #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:
// // 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__
#include "../common/initsock.h" #include "../common/protoinfo.h" #include <stdio.h> #include <mstcpip.h> #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); }
运行结果