网络嗅探器
网卡的四种工作模式
①广播模式:该模式下的网卡能够接收网络中的广播信息;
②组播模式:设置在该模式下的网卡能够接收组播数据;
③直接模式:在这种模式下,只有目的网卡才能接收该数据;
④混杂模式:在这种模式下的网卡能够接收一切通过它的数据,而不管该数据是否是传给它的
嗅探器原理:
嗅探器作为一种网络通讯程序,也是通过对网卡的编程来实现网络通讯的,对网卡的编程也是使用通常的套接字(socket)方式来进行。但是,通常的套接字程序只能响应与自己硬件地址相匹配的或是以广播形式发出的数据帧,对于其他形式的数据帧比如已到达网络接口但却不是发给此地址的数据帧,网络接口在验证投递地址并非自身地址之后将不引起响应,也就是说应用程序无法收取到达的数据包。而网络嗅探器的目的恰恰在于从网卡接收所有经过它的数据包,这些数据包即可以是发给它的也可以是发往别处的。显然,要达到此目的就不能再让网卡按通常的正常模式工作,而必须将其设置为混杂模式。
具体到编程实现上,这种对网卡混杂模式的设置是通过原始套接字(rawsocket)来实现的,这也有别于通常经常使用的数据流套接字和数据报套接字。在创建了原始套接字后,需要通过setsockopt()函数来设置IP头操作选项,然后再通过bind()函数将原始套接字绑定到本地网卡。为了让原始套接字能接受所有的数据,还需要通过ioctlsocket()来进行设置,而且还可以指定是否亲自处理IP头。至此,实际就可以开始对网络数据包进行嗅探了,对数据包的获取仍象流式套接字或数据报套接字那样通过recv()函数来完成。但是与其他两种套接字不同的是,原始套接字此时捕获到的数据包并不仅仅是单纯的数据信息,而是包含有IP头、TCP头等信息头的最原始的数据信息,这些信息保留了它在网络传输时的原貌。通过对这些在低层传输的原始信息的分析可以得到有关网络的一些信息。由于这些数据经过了网络层和传输层的打包,因此需要根据其附加的帧头对数据包进行分析。
代码详解
首先创建原始套接字,将原始套接字绑定到一个明确的本地地址(不能为ADDR_ANY),然后通过ioctlsocket()函数(是控制套接口的模式),向套接字发送SIO_RCVALL控制命令,将网卡设置为混杂模式,这样就能接收到所有经过网卡的封包;然后已连接的数据报或流式套接口通过不断调用recv()函数来接收IP数据包,将接收到的数据存入缓冲数组;随后再通过自定义的IP数据包解析函数对接收到的数据进行解析,通过定义的IP地址结构体的指针对象从接收到的数据中取得数据包的IP头;然后从IP头中取出源IP地址和目的IP地址。
IP
TCP
注意先后顺序,后面之所以可以直接调用
是因为在定义IP TCP头的时候用了unsigned short/long/char/… 他们的长度是固定的,这样在调用的时候可以直接调用属性,而不用写其他的return方法;
/*
定义协议格式
定义协议中使用的宏
*/
#ifndef __PROTOINFO_H__
#define __PROTOINFO_H__
#define ETHERTYPE_IP 0x0800
// 协议
typedef struct _IPHeader // 20字节的IP头
{
unsigned char iphVerLen; // 版本号和头长度(各占4位)
unsigned char ipTOS; // 服务类型
unsigned short ipLength; // 封包总长度,即整个IP报的长度
unsigned short ipID; // 封包标识,惟一标识发送的每一个数据报
unsigned short ipFlags; // 标志
unsigned char ipTTL; // 生存时间,就是TTL
unsigned char ipProtocol; // 协议,可能是TCP、UDP、ICMP等
unsigned short ipChecksum; // 校验和
unsigned long ipSource; // 源IP地址
unsigned long 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头
{
unsigned short sourcePort; // 16位源端口号
unsigned short destinationPort; // 16位目的端口号
unsigned long sequenceNumber; // 32位序列号
unsigned long acknowledgeNumber; // 32位确认号
unsigned char dataoffset; // 高4位表示数据偏移
unsigned char flags; // 6位标志位
unsigned short windows; // 16位窗口大小
unsigned short checksum; // 16位校验和
unsigned short urgentPointer; // 16位紧急数据偏移量
} TCPHeader, *PTCPHeader;
typedef struct _UDPHeader
{
unsigned short sourcePort; // 源端口号
unsigned short destinationPort;// 目的端口号
unsigned short len; // 封包长度
unsigned short checksum; // 校验和
} UDPHeader, *PUDPHeader;
#endif // __PROTOINFO_H#pragma once
这个函数是必须要的,我们考虑过把它加进.cpp文件中,像之前socket一样,但是不行,个人以为是原始套接字的原因。
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib") // 链接到WS2_32.lib库文件
class CInitSock {
public:
CInitSock(BYTE minorVer = 2, BYTE majorVer = 2) {
// 初始化WS2_32.dll
WSADATA wsaData;
word sockVersion = MAKEWORD(minorVer, majorVer); //Winsock版本
/*为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过wsastartup函数完成对 Winsock服务的初始化,因此需要调用WSAStartup函数。
使用Socket的程序在使用Socket之前必须调用WSAStartup函数。
该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的Socket的版本信息。当一个应用程序调用WSAStartup函数时,
操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。*/
if (::WSAStartup(sockVersion, &wsaData) != 0) { //(Windows异步套接字的启动命令)加载套接字库
exit(0);
}
}
~CInitSock() { //析构函数
::WSACleanup(); //关闭加载的套接字库
}
};
#include "initsock.h"
#include "protoinfo.h"
//#include <stdio.h>
#include <mstcpip.h> //用于处理TCP/ip协议的一个头文件,这里面包含了WinSockets一系列相关函数.
#include<iOStream>
//#include<stdlib.h>
#include <WinSock2.h>
#pragma comment(lib, "Advapi32.lib") //链接Advapi32.lib库文件
#pragma comment(lib,"ws2_32.lib")
using namespace std;
//CInitSock theSock; //创建CInitSock类的对象
//自定义的函数,用于解析收到的TCP数据封包,解析出封包中的TCP头
void DecodeTCPPacket(char *pData) {
TCPHeader *pTCPHdr = (TCPHeader *)pData;
/*ntohs()是一个函数名,作用是将一个16位数由网络字节顺序转换为主机字节顺序*/
cout<<" Port:"<<ntohs(pTCPHdr->sourcePort)<<" -> "<<ntohs(pTCPHdr->destinationPort)<<endl;
// 下面还可以根据目的端口号进一步解析应用层协议
switch (::ntohs(pTCPHdr->destinationPort)) {
case 21: //FTP
break;
case 80: //网页
case 8080: //WWW代理服务
break;
}
}
void DecodeUDPPacket(char *pData)
{
UDPHeader *pUDPHdr = (UDPHeader *)pData;
/*ntohs()是一个函数名,作用是将一个16位数由网络字节顺序转换为主机字节顺序*/
cout << " Port:" << ntohs(pUDPHdr->sourcePort) << " -> " << ntohs(pUDPHdr->destinationPort) << endl;
}
/*
自定义的函数,用于解析收到的IP数据封包,解析出封包中的IP头
*/
void DecodeIPPacket(char *pData) {
IPHeader *pIPHdr = (IPHeader*)pData;
in_addr source, dest; //in_addr是一个结构体,可以用来表示一个32位的IPv4地址
char szSourceIp[32], szDestIp[32]; // 定义两个数组用于存储源IP地址和目的IP地址
cout<<"*************************************************************************"<<endl;
// 从IP头中取出源IP地址和目的IP地址
source.S_un.S_addr = pIPHdr->ipSource; // 从IP头中取出源IP地址
dest.S_un.S_addr = pIPHdr->ipDestination; // 从IP头中取出目的IP地址
strcpy(szSourceIp, ::inet_ntoa(source));
strcpy(szDestIp, ::inet_ntoa(dest));
cout<<szSourceIp<<" ---> "<<szDestIp<<endl; //打印出源IP地址和目的IP地址
cout << "版本号:"<<(int)pIPHdr->iphVerLen<<endl;
cout << "服务类型:" <<(int) pIPHdr->ipTOS << endl;
cout << "封包总长度:" << pIPHdr->ipLength << endl;
cout << "封包标识:" << pIPHdr->ipID << endl;
cout << "生存时间:" << (int)pIPHdr->ipTTL << endl;
cout << "协议类型:" <<(int) pIPHdr->ipProtocol << endl;
cout << "校验和:" << pIPHdr->ipChecksum << endl; // IP头长度
int nHeaderLen = (pIPHdr->iphVerLen & 0xf) * sizeof(ULONG);
switch (pIPHdr->ipProtocol)
{
case IPPROTO_TCP: // TCP协议
DecodeTCPPacket(pData + nHeaderLen);
break;
case IPPROTO_UDP:
DecodeUDPPacket(pData + nHeaderLen);
break;
case IPPROTO_ICMP:
break;
}
}
int main() {
// 创建原始套节字sRaw
WSADATA wsaData;//WSADATA结构被用来保存函数WSAStartup返回的Windows Sockets初始化信息
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) //加载套接字库
{
printf("failed to load Winsock");
return 0;
}
SOCKET sRaw = socket(AF_INET, SOCK_RAW, IPPROTO_IP);//必须在管理员模式下运行
if (sRaw == SOCKET_ERROR) {
printf("创建套接字失败:%d", WSAGetLastError());
}
// 获取本地IP地址
char szHostName[56];
sockaddr_in addr_in;
struct hostent *pHost; //hostent是host entry的缩写,该结构记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表
gethostname(szHostName, 56);
if ((pHost = gethostbyname((char*)szHostName)) == NULL)
cout << "get failed";
// 在调用ioctl之前,套节字必须绑定
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(7000);
//addr_in.sin_addr.S_un.S_addr = inet_addr(" 127.0.0.1");
memcpy(&addr_in.sin_addr.S_un.S_addr, pHost->h_addr_list[0], pHost->h_length);
cout<<" Binding to interface :"<<inet_ntoa(addr_in.sin_addr)<<endl;
if (bind(sRaw, (PSOCKADDR)&addr_in, sizeof(addr_in)) == SOCKET_ERROR) //将原始套接字绑定到一个明确的本机地址
cout << "bind failed"<<endl;
// 设置SIO_RCVALL控制代码,以便接收所有的IP包
dword dwValue = 1;
/*
ioctlsocket()是控制套接口的模式,向套接字发送SIO_RCVALL控制命令,将网卡设置为混杂模式,这样就能接收到所有经过网卡的封包
网卡的四种接收模式:
①广播模式:该模式下的网卡能够接收网络中的广播信息;
②组播模式:设置在该模式下的网卡能够接收组播数据;
③直接模式:在这种模式下,只有目的网卡才能接收该数据;
④混杂模式:在这种模式下的网卡能够接收一切通过它的数据,而不管该数据是否是传给它的
*/
if (ioctlsocket(sRaw, SIO_RCVALL, &dwValue) != 0) ////向套接字发送SIO_RCVALL控制命令,将网卡设置为混杂模式,这样就能接收到所有经过网卡的封包
cout << "ioct failed"<<endl;
// 开始接收封包
char buff[1024]; //用于接受数据的数组
int nRet;
while (true)
{
nRet = recv(sRaw, buff, 1024, 0); //recv函数接收消息出错的话,会返回 0
if (nRet > 0) {
DecodeIPPacket(buff); //调用该函数对IP封包进行解析
}
}
int a;
system("pause");
cin >> a;
closesocket(sRaw); //关闭原始套接字
return 0;
}
相关阅读
今天怎么来讨论一个话题,那就是网络赚钱。是的,是靠网络来赚钱的工作。因为有些人一听说网络赚钱,第一个反应就是诶,那是骗人的,不可能
转自原文: 本文主要内容包括: (1) 介绍神经网络基本原理,(2) AForge.NET实现前向神经网络的方法,(3) Matlab实现前向神经网络的方法
TCP/IP详解05-网络层:ICMP协议、Ping和Traceroute
TCP/IP详解05-网络层:ICMP协议、Ping和Traceroute TCP/IP详解05-网络层:ICMP协议、Ping和Traceroute 1. ICMP:Internet控制报文协
互联网营销是由互联网的快速发展产生的。电子商务,广告信息的发布,网站的营销等,网络营销模式越来越多样化,越来越多的企业意识到网络