1.两者实现的区别
用 Raw Socket 实现 Sniffer 的方法,实现起来比较简单,但有个缺点就是只能截获 IP 层以上的包,数据包头不含帧信息。但是相对于来说实现起来会比较简单。使用的话就见仁见智了,根据自己的需要去选择即可。
2.Raw Socket
操作环境:vs2005 其他的环境下代码有可能需要改动
#include
#include
#include
#include "fstream"
#include
using namespace std;
#pragma comment (lib,"ws2_32.lib")
#define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)
#define IP_HDRINCL 2
struct IPHEAD
{
unsigned char h_len:4;//4位首部长度+4位IP版本号
unsigned char ver:4;
unsigned char tos;//8位服务类型TOS
unsigned short total_len;//16位总长度(字节)
unsigned short ident;//16位标识
unsigned short frag_and_flags;//3位标志位
unsigned char ttl;//8位生存时间 TTL
unsigned char proto;//8位协议 (TCP, UDP 或其他)
unsigned short checksum;//16位IP首部校验和
unsigned int sourceip;//32位源IP地址
unsigned int destip;//32位目的IP地址
};
struct TCPHEAD //定义TCP首部
{
USHORT th_sport; //16位源端口
USHORT th_dport; //16位目的端口
unsigned int th_seq; //32位序列号
unsigned int th_ack; //32位确认号
unsigned char th_lenres; //4位首部长度/6位保留字
unsigned char th_flag; //6位标志位
USHORT th_win; //16位窗口大小
USHORT th_sum; //16位校验和
USHORT th_urp; //16位紧急数据偏移量
};
char *phostlist[10];//列举主机网卡的数组
DWORD _stdcall listen(void *p)
{
SOCKET s;
struct sockaddr_in addr;
int itimeout=10000;
int ret;
BOOL flag;
char cbuf[1500];//接收数据缓冲区
struct IPHEAD *piphd;//定义IP头结构
struct TCPHEAD *ptcphd;//定义TCP头结构
s=socket(AF_INET,SOCK_RAW,IPPROTO_IP); //创建一个原始套接字
if (s == INVALID_SOCKET)
{
printf("创建原始套接字失败\n");
system("pause");
}
//设置 IP 头操作选项
setsockopt(s,IPPROTO_IP,IP_HDRINCL,(char*)&flag,sizeof(flag));
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_addr.S_un.S_addr=inet_addr((char *)p);
addr.sin_port=htons(6000);//设置本地端口号
bind(s,(struct sockaddr *)&addr,sizeof(addr));//绑定端口
//设置sock_raw为sio_rcvall,以便接收所有IP包
DWORD dwin=1;
ioctlsocket(s,SIO_RCVALL,&dwin);
for(;;)
{
memset(cbuf,0,sizeof(cbuf));
ret=recv(s,cbuf,sizeof(cbuf),0);//接收数据
if(ret==SOCKET_ERROR)
{
if(WSAGetLastError()==WSAETIMEDOUT)continue;
closesocket(s);
return 0;
}
piphd=(struct IPHEAD *)cbuf;//取得IP头数据的地址
int iIphLen = sizeof(unsigned long) * (piphd->h_len & 0xf);
ptcphd=(struct TCPHEAD *)(cbuf+iIphLen);//取得TCP头数据的地址
printf("From : %s \t port %d\t",inet_ntoa(*(struct in_addr*)&piphd->sourceip),ntohs(ptcphd->th_sport) );
printf("To : %s \t port %d ",inet_ntoa(*(struct in_addr*)&piphd->destip),ntohs(ptcphd->th_dport));
switch(piphd->proto)//根据IP头的协议判断数据包协议类型
{
case 1:
printf("ICMP\n");
break;
case 2:
printf("IGMP\n");
break;
case 6:
printf("TCP\n");
break;
case 17:
printf("UDP\n");
break;
default:
printf("unknow:%d\n",piphd->proto);
}
}
return 1;
}
int main()
{
//初始化sock
WSADATA wsa;
int i=0;
DWORD dwtid;
char chname[128];
hostent *host;
WSAStartup(MAKEWORD(2,2),&wsa);
gethostname(chname,sizeof(chname));
host=gethostbyname(chname);
while(host->h_addr_list[i]!=NULL)//取所有网卡序号,为每个网卡开启一个监听线程
{
phostlist[i]=(char *)malloc(16);
sprintf(phostlist[i],"%s",inet_ntoa(*(struct in_addr *)host->h_addr_list[i]));
printf("Bind to %s\n",phostlist[i]);
CreateThread(NULL,0,listen,phostlist[i],0,&dwtid); //用于定义新线程的安全属性,一般设置成NULL 分配以字节数表示的线程堆栈的大小,默认值是0; 返回新创建的线程的ID编号
i++;
}
for(;;)//为每个网卡创建监听线程后要用一个循环防止主线程退出
{
Sleep(10);
}
}
主要函数:
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)
setsockopt(s,IPPROTO_IP,IP_HDRINCL,(char*)&flag,sizeof(flag));
设置套接字的选项。具体的可以看这个博文https://www.cnblogs.com/eeexu123/p/5275783.html
本程序设置SockRaw这个套接字的ip选项中的IP_HDRINCL即在数据包中包含IP首部。
int ioctlsocket( SOCKET s, long cmd, u_long FAR *argp );
s:一个标识套接口的描述字。
cmd:对套接口s的操作命令。
argp:指向cmd命令所带参数的指针。
即设置sock_raw为sio_rcvall,以便接收所有IP包
3.winpcap
关于这个的代码到处都是,我也是直接在网上粘了一段,稍微改了一下。
1)配置环境
参考自:https://blog.csdn.net/gk405128494/article/details/39314467
1.到http://www.winpcap.org/install/default.htm下载winpcap的安装包,然后到http://www.winpcap.org/devel.htm程序员开发包。
2.执行安装包,这样你的机子就能运行winpcap程序了。
3.解压开发包,在VC6.0的Tools–>Option–>Directories的Include fils 和library files加入winpcap的include和lib目录。
4.开始编写wpcap程序。
vs2005:
第一步:下载WinPcap的安装包;有不同操作系统环境下的包,我下的是win32版本的。下载地址:www.winpcap.org;最新的Release版本是4.0.1的,最高的版本一般是Beta的。这个安装包主要是注册一个wpcap.dll的库到操作系统中。必须安装,如果不安装,在运行例子的时候会弹出窗口提示,找不到wpcap.dll文件;
第二步:到上面的网站下载它的开发包,包括一些头文件和库文件;解压到自己指定的目录中;目录中还有HTML格式的说明文档,用于自己学习比较方便;
第三步:设置VS2005;1)设置环境目录;在菜单:工具->选项;弹出的选项窗体左边点击:项目和解决方案->VC++目录;在右边:“显示以下内容的目录”标签下面的下拉框中找到“包含文件”,然后对应到第二步下载开发包的Include目录;在同一下拉框中找到“库文件”,然后对应到第二步下载开发包的lib目录;2)设置编译条件;在项目属性页中:配置属性->C/C++->预处理器->预处理器定义,增加;WPCAP;HAVE_REMOTE;每一个预定义符用”;”隔开;在项目属性页中:配置属性->链接器->命令行->附加选项对应的文本框中增加:“wpcap.lib ws2_32.lib”;
第四步:非必要步骤;有的时候可能会有些意外错误;比如找不到u_char类型等;我的解决办法是加上
这里写代码片
#ifndef WIN32
#include
#include
#else
#include
#endif
2)代码段:
参考自:http://doc.okbase.net/29062706/archive/10224.html
正则表达式在设置时一定要注意
#define WIN32
#include
#include
#ifndef WIN32
#include
#include
#else
#include
#endif
#pragma comment(lib, "wpcap.lib")
/* 4 字节IP地址 */
typedef struct ip_address
{
u_char byte1;
u_char byte2;
u_char byte3;
u_char byte4;
}ip_address;
/*
// IP包如下所示,请参见 RFC791.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
/* IPv4 头 */
typedef struct ip_header
{
u_char ver_ihl; //4位首部长度,4位IP版本号
u_char tos; //8位服务类型TOS
u_short tlen; //16位总长度(字节)
u_short identification; //16位标识
u_short flags_fo; //3位标志位,13位偏移
u_char ttl; //8位生存时间 TTL
u_char proto; //8位协议 (TCP, UDP 或其他)
u_short crc; //16位IP首部校验和
ip_address saddr; //32位源IP地址
ip_address daddr; //32位目的IP地址
u_int op_pad; //32位选项
}ip_header;
/*
// TCP包如下所示,请参见 RFC793.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
//定义TCP首部
typedef struct tcp_header
{
u_short sport; //16位源端口
u_short dport; //16位目的端口
u_int seq; //32位序列号
u_int ack; //32位确认号
u_char lenres; //4位首部长度/6位保留字
u_char flag; //6位标志位
u_short win; //16位窗口大小
u_short sum; //16位校验和
u_short urp; //16位紧急数据偏移量
u_int op_pad; //32位选项
}tcp_header;
/*
// UDP包如下所示,请参见 RFC768.
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+
| Source | Destination |
| Port | Port |
+--------+--------+--------+--------+
| | |
| Length | Checksum |
+--------+--------+--------+--------+
|
| data octets ...
+---------------- ...
*/
/* UDP header */
typedef struct udp_header
{
u_short sport; //16位源端口
u_short dport; //16位目的端口
u_short len; //16位长度
u_short crc; //16位校验和
}udp_header;
// sniffer.c : 定义控制台应用程序的入口点。
int main(int argc, char **argv)
{
pcap_if_t* alldevs;
pcap_if_t* d;
int inum;
int i = 0;
pcap_t* fp;
char *ofilename = "sniffer.txt";
char errbuf[PCAP_ERRBUF_SIZE];//存储错误信息
u_int netmask;//掩码
int res;
struct pcap_pkthdr *header;
const u_char *pkt_data;
char packet_filter[] = "tcp or udp";
pcap_dumper_t *dumpfile;
struct bpf_program fcode;
ip_header* ih;
tcp_header* th;
udp_header* uh;
u_int ip_len;
u_short sport, dport;
// 获得网络设备指针
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr, "pcap_findalldevs出错: %s\n", errbuf);
exit(1);
}
// 枚举网卡信息,首地址
for (d = alldevs; d; d = d->next)
{
printf("%d. %s", ++ i, d->name);
if (d->description)
{
printf(" (%s)\n", d->description);
}
else
{
printf(" (无描述信息)\n");
}
}
if (i == 0)
{
printf("\n没发现任何接口,请确认是否已经安装WinPcap库.\n");
return -1;
}
printf("请输入网卡接口号 (1 - %d):", i);
scanf("%d", &inum);
if (inum < 1 || inum > i)
{
printf("\n接口号超出范围.\n");
// 释放alldevs资源
pcap_freealldevs(alldevs);
return -1;
}
// 跳到选择的网卡
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
// 打开网卡
if ((fp = pcap_open(d->name, // 设备名称
65536,
PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式
1000, // 读取超时时间
NULL, // 远程认证,本程序因是本地嗅探,不需要设置
errbuf
)) == NULL)
{
fprintf(stderr, "\nUnable to open the adapter. %s is not supported by Winpcap\n");
// 释放alldevs资源
pcap_freealldevs(alldevs);
return -1;
}
// 检查链路层,本程序只简单的支持以太网
if (pcap_datalink(fp) != DLT_EN10MB)
{
fprintf(stderr, "\n本程序只简单的支持以太网.\n");
// 释放alldevs资源
pcap_freealldevs(alldevs);
return -1;
}
if (d->addresses != NULL)
{
// 取得一个网络接口的子网掩码
netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
}
else
{
netmask = 0xffffffff;
}
// 编译过滤器
if (pcap_compile(fp, &fcode, packet_filter, 1, netmask) < 0)
{
fprintf(stderr, "\nU不能编译包过滤,请检查正则表达式.\n");
// 释放alldevs资源
pcap_freealldevs(alldevs);
return -1;
}
// 设置过滤器
if (pcap_setfilter(fp, &fcode) < 0)
{
fprintf(stderr, "\n设置过滤器失败.\n");
// 释放alldevs资源
pcap_freealldevs(alldevs);
return -1;
}
printf("\nlistening on %s ...\n", d->description);
// 释放alldevs资源,因为不再需要它
pcap_freealldevs(alldevs);
// 打开输出文件
dumpfile= pcap_dump_open(fp, ofilename);
if (dumpfile == NULL)
{
fprintf(stderr,"\n打开输出文件错误\n");
pcap_close(fp);
return -1;
}
// 开始捕获数据包
while((res = pcap_next_ex( fp, &header, &pkt_data)) >= 0)
{
if(res == 0)
/* 超时继续 */
continue;
// 将捕获的数据包存入文件
pcap_dump((unsigned char *) dumpfile, header, pkt_data);
// 取得IP头,14为以太网头长度
ih = (ip_header*)(pkt_data + 14);
ip_len = (ih->ver_ihl & 0xf) * 4;
if (ih->proto == 6) //TCP
{
//取TCP头
th = (tcp_header*)((u_char*)ih + ip_len);
/* convert from network byte order to host byte order */
sport = ntohs(th->sport); //网络字节序转主机字节序
dport = ntohs(th->dport); //网络字节序转主机字节序
/* 打印IP地址和端口号 */
printf("%d.%d.%d.%d:%d-> %d.%d.%d.%d:%d\n",
ih->saddr.byte1,
ih->saddr.byte2,
ih->saddr.byte3,
ih->saddr.byte4,
sport,
ih->daddr.byte1,
ih->daddr.byte2,
ih->daddr.byte3,
ih->daddr.byte4,
dport);
}
else if (ih->proto == 17) //UDP
{
//取UDP头
uh = (udp_header*)((u_char*)ih + ip_len);
/* convert from network byte order to host byte order */
sport = ntohs(uh->sport); //网络字节序转主机字节序
dport = ntohs(uh->dport); //网络字节序转主机字节序
/* 打印IP地址和端口号 */
printf("%d.%d.%d.%d:%d-> %d.%d.%d.%d:%d\n",
ih->saddr.byte1,
ih->saddr.byte2,
ih->saddr.byte3,
ih->saddr.byte4,
sport,
ih->daddr.byte1,
ih->daddr.byte2,
ih->daddr.byte3,
ih->daddr.byte4,
dport);
}
}
pcap_close(fp);
pcap_dump_close(dumpfile);
return 0;}