一般都熟悉sniffer这个工具,它可以捕捉流经本地网卡的所有数据包。抓取网络数据包进行分析有很多用处,如分析网络是否有网络病毒等异常数据,通信协议的分析(数据链路层协议、IP、UDP、TCP、甚至各种应用层协议),敏感数据的捕捉等。下面我们就来看看在windows下如何实现数据包的捕获。

下面先对网络嗅探器的原理做简单介绍。

嗅探器设计原理

  嗅探器作为一种网络通讯程序,也是通过对网卡的编程来实现网络通讯的,对网卡的编程也是使用通常的套接字(socket)方式来进行。但是,通常的套接字程序只能响应与自己硬件地址相匹配的或是以广播形式发出的数据帧,对于其他形式的数据帧比如已到达网络接口但却不是发给此地址的数据帧,网络接口在验证投递地址并非自身地址之后将不引起响应,也就是说应用程序无法收取到达的数据包。而网络嗅探器的目的恰恰在于从网卡接收所有经过它的数据包,这些数据包即可以是发给它的也可以是发往别处的。显然,要达到此目的就不能再让网卡按通常的正常模式工作,而必须将其设置为混杂模式。

  具体到编程实现上,这种对网卡混杂模式的设置是通过原始套接字(raw socket)来实现的,这也有别于通常经常使用的数据流套接字和数据报套接字。在创建了原始套接字后,需要通过setsockopt()函数来设置IP头操作选项,然后再通过bind()函数将原始套接字绑定到本地网卡。为了让原始套接字能接受所有的数据,还需要通过ioctlsocket()来进行设置,而且还可以指定是否亲自处理IP头。至此,实际就可以开始对网络数据包进行嗅探了,对数据包的获取仍象流式套接字或数据报套接字那样通过recv()函数来完成。但是与其他两种套接字不同的是,原始套接字此时捕获到的数据包并不仅仅是单纯的数据信息,而是包含有 IP头、 TCP头等信息头的最原始的数据信息,这些信息保留了它在网络传输时的原貌。通过对这些在低层传输的原始信息的分析可以得到有关网络的一些信息。由于这些数据经过了网络层和传输层的打包,因此需要根据其附加的帧头对数据包进行分析。下面先给出结构.数据包的总体结构:

数据包
IP头 TCP头(或其他信息头) 数据

数据在从应用层到达传输层时,将添加TCP数据段头,或是UDP数据段头。其中UDP数据段头比较简单,由一个8字节的头和数据部分组成,具体格式如下:

16位 16位
源端口 目的端口
UDP长度 UDP校验和

而TCP数据头则比较复杂,以20个固定字节开始,在固定头后面还可以有一些长度不固定的可选项,下面给出TCP数据段头的格式组成:

16位 16位
源端口 目的端口
        顺序号
        确认号
TCP头长 (保留)7位 URG ACK PSH RST SYN FIN 窗口大小
校验和 紧急指针
           可选项(0或更多的32位字)
           数据(可选项)

对于此TCP数据段头的分析在编程实现中可通过数据结构_TCP来定义:

typedef struct _TCP{
WORD SrcPort;  // 源端口
WORD DstPort;  // 目的端口
DWORD SeqNum;  // 顺序号
DWORD AckNum;  // 确认号
BYTE DataOff;  // TCP头长
BYTE Flags;   // 标志(URG、ACK等)
WORD Window;   // 窗口大小
WORD Chksum;   // 校验和
WORD UrgPtr;   // 紧急指针
} TCP;
typedef TCP *LPTCP;
typedef TCP UNALIGNED * ULPTCP;

网络层,还要给TCP数据包添加一个IP数据段头以组成IP数据报。IP数据头以大端点机次序传送,从左到右,版本字段的高位字节先传输(SPARC是大端点机;Pentium是小端点机)。如果是小端点机,就要在发送和接收时先行转换然后才能进行传输。IP数据段头格式如下:

16位 16位
版本 IHL 服务类型 总长
标识 标志 分段偏移
生命期 协议 头校验和
源地址
目的地址
选项(0或更多)

同样,在实际编程中也需要通过一个数据结构来表示此IP数据段头,下面给出此数据结构的定义:

typedef struct _IP{
union{
BYTE Version;   // 版本
BYTE HdrLen;    // IHL
};
BYTE ServiceType;   // 服务类型
WORD TotalLen;     // 总长
WORD ID;        // 标识
union
{
WORD Flags;    // 标志
WORD FragOff;   // 分段偏移
};
BYTE TimeToLive;    // 生命期
BYTE Protocol;     // 协议
WORD HdrChksum;    // 头校验和
DWORD SrcAddr;    // 源地址
DWORD DstAddr;     // 目的地址
BYTE Options;     // 选项
} IP;
typedef IP * LPIP;
typedef IP UNALIGNED * ULPIP;

在明确了以上几个数据段头的组成结构后,就可以对捕获到的数据包进行分析了。

嗅探器实现

嗅探器实质就是从网络上获取数据包的一种工具,它可以捕捉流经本地网卡的所有数据包。抓取网络数据包进行分析有很多用处,如分析网络是否有网络病毒等异常数据,通信协议的分析(数据链路层协议、IP、UDP、TCP、甚至各种应用层协议),敏感数据的捕捉等。下面我们就来看看在windows下如何实现数据包的捕获。

WINSOCK本身就提供了抓取流经网卡的所有数据包的函数,虽然只能在IP协议层上捕捉,但只要您的工作没有涉及到数据链路层的话,这也就足够用了。抓取数据包的编程方法基本和编写其它网络应用程序一样,只需多一个步骤,即将SOCKET设置为接收所有数据的模式,这是用WSAIoctl来实现的。

编程实现主要有以下几个步骤:
    1. 初始化WINSOCK库;
    2. 创建SOCKET句柄;
    3. 绑定SOCKET句柄到一个本地地址;
    4. 设置该SOCKET为接收所有数据的模式;
    5. 接收数据包;
    6. 关闭SOCKET句柄,清理WINSOCK库;

(1)初始化winsock库

Winsock是Windows下的网络编程接口,它是由Unix下的BSD Socket发展而来,是一个与网络协议无关的编程接口。Winsock在常见的Windows平台上有两个主要的版本,即Winsock1和Winsock2。编写与Winsock1兼容的程序你需要引用头文件WINSOCK.H,如果编写使用Winsock2的程序,则需要引用WINSOCK2.H。此外还有一个MSWSOCK.H头文件,它是专门用来支持在Windows平台上高性能网络程序扩展功能的。使用WINSOCK.H头文件时,同时需要库文件WSOCK32.LIB,使用WINSOCK2.H时,则需要WS2_32.LIB,如果使用MSWSOCK.H中的扩展API,则需要MSWSOCK.LIB。正确引用了头文件,并链接了对应的库文件,你就构建起编写WINSOCK网络程序的环境了。

每个Winsock程序必须使用WSAStartup载入合适的Winsock动态链接库,如果载入失败,WSAStartup将返回SOCKET_ERROR,这个错误就是WSANOTINITIALISED,WSAStartup的定义如下:

int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);

wVersionRequested指定了你想载入的Winsock版本,其高字节指定了次版本号,而低字节指定了主版本号。你可以使用宏MAKEWORD(x, y)来指定版本号,这里x代表主版本,而y代表次版本。lpWSAData是一个指向WSAData结构的指针,WSAStartup会向该结构中填充其载入的Winsock动态链接库的信息。

当你使用完Winsock接口后,要调用下面的函数对其占用的资源进行释放:

int WSACleanup(void);

如果调用该函数失败也没有什么问题,因为操作系统为自动将其释放,对应于每一个WSAStartup调用都应该有一个WSACleanup调用.

错误处理
    
   Winsock函数调用失败大多会返回 SOCKET_ERROR(实际上就是-1),你可以调用WSAGetLastError得到错误的详细信息:

int WSAGetLastError (void);

对该函数的调用将返回一个错误码,其码值在WINSOCK.H或WINSOCK2.H(根据其版本)中已经定义,这些预定义值都以WSAE开头.同时你还可以使用WSASetLastError来自定义错误码值.

下面是我的winsock初始化例子:

WORD wVersion;
WSADATA wsadata;
int err;
wVersion = MAKEWORD(,);
// WSAStartup() initiates the winsock,if successful,the function returns zero
err = ::WSAStartup(wVersion,&wsadata);
if(err!=)
{
printf("Couldn't initiate the winsock!\n");
}

【注意】使用WORD 、WSADATA 和WSAStartup等时必须包含其头文件,加入语句:

#include "winsock2.h"
#include "windows.h"

我在初始化winsock库时遇到了一个问题,调用WSAStartUp(),编译后出现unresolved external symbol_WSAStartup@8,开始觉得很奇怪,因为头文件之类的都包含了怎么会出错呢?后来上网查了下才知道,需要包含一个动态链接库WS2_32.LIB,方法有两种:

第一种:

在菜单   project ->settings -> link  -> object/library   modules   下面输入ws2_32.lib  然后确定即可

第二种:

在头文件中加入语句#pragma   comment(   lib,   "ws2_32.lib"   )  来显式加载。 即:

#include  <winsock2.h>
#pragma comment(lib, "WS2_32")

(2)创建socket句柄

winsock库初始化成功后就可以创建socket句柄了,使用函数socket即可。socket函数原型为:

int socket(int domain, int type, int protocol); 

domain指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族);type参数指定socket的类型:SOCK_STREAM 或SOCK_DGRAM,Socket接口还定义了原始Socket(SOCK_RAW),允许程序使用低层协议;protocol通常赋值"0"。Socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。

创建了socket句柄后,要将该句柄与本地IP绑定后才能使用。在进行绑定之前,要先获得本地机器的相关信息,包括主机名,主机IP地址等。最后进行绑定。我的代码如下:

SOCKET ServerSock=socket(AF_INET,SOCK_RAW,IPPROTO_IP);
char mname[];
struct hostent* pHostent;
sockaddr_in myaddr;
//Get the hostname of the local machine
if( - == gethostname(mname, sizeof(mname)))
{
closesocket(ServerSock);
printf("%d",WSAGetLastError());
exit(-);
}
else
{
//Get the IP adress according the hostname and save it in pHostent
pHostent=gethostbyname((char*)mname);
//填充sockaddr_in结构
myaddr.sin_addr = *(in_addr *)pHostent->h_addr_list[];
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons();//对于IP层可随意填
//bind函数创建的套接字句柄绑定到本地地址
if(SOCKET_ERROR==bind(ServerSock,(struct sockaddr *)&myaddr,sizeof(myaddr)))
{
closesocket(ServerSock);
cout<<WSAGetLastError<<endl;
exit(-);
}
}

接下来的工作就是把该socket设为接收所有数据的模式。

//设置该SOCKET为接收所有流经绑定的IP的网卡的所有数据,包括接收和发送的数据包
u_long sioarg = ;
DWORD dwValue=;
if( SOCKET_ERROR == WSAIoctl( ServerSock, SIO_RCVALL , &sioarg,sizeof(sioarg),NULL,,&dwValue,NULL,NULL ) )
{
closesocket(ServerSock);
cout << WSAGetLastError();
exit(-);
}

接收网络数据的工作了。

【注意】这里一定要保证gethostname、gethostbyname、bind、ioctlsocket等函数都能够被正确执行,我在开始时就因为几个参数设置不对而导致bind和ioctlsocket执行错误,费了半天周折才搞定。

以下是我的完整代码:

WORD wVersion;
WSADATA wsadata;
int err;
wVersion = MAKEWORD(,);
// WSAStartup() initiates the winsock,if successful,the function returns zero
err = ::WSAStartup(wVersion,&wsadata);
if(err!=)
{
printf("Couldn't initiate the winsock!\n");
}
else
{
// create a socket
SOCKET ServerSock=socket(AF_INET,SOCK_RAW,IPPROTO_IP);
char mname[];
struct hostent* pHostent;
sockaddr_in myaddr;
//Get the hostname of the local machine
if( - == gethostname(mname, sizeof(mname)))
{
closesocket(ServerSock);
printf("%d",WSAGetLastError());
exit(-);
}
else
{
//Get the IP adress according the hostname and save it in pHostent
pHostent=gethostbyname((char*)mname);
//填充sockaddr_in结构
myaddr.sin_addr = *(in_addr *)pHostent->h_addr_list[];
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons();//对于IP层可随意填
//bind函数创建的套接字句柄绑定到本地地址
if(SOCKET_ERROR==bind(ServerSock,(struct sockaddr *)&myaddr,sizeof(myaddr)))
{
closesocket(ServerSock);
cout<<WSAGetLastError<<endl;
printf("..............................Error……");
getchar();
exit(-);
} //设置该SOCKET为接收所有流经绑定的IP的网卡的所有数据,包括接收和发送的数据包
u_long sioarg = ;
DWORD dwValue=;
if( SOCKET_ERROR == WSAIoctl( ServerSock, SIO_RCVALL , &sioarg,sizeof(sioarg),NULL,,&dwValue,NULL,NULL ) )
{
closesocket(ServerSock);
cout << WSAGetLastError();
exit(-);
}
//获取分析数据报文
char buf[];
int len = ;
listen(ServerSock,);
do
{
len = recv( ServerSock, buf, sizeof(buf),);
if( len > )
{
//报文处理
}
}while( len > );
}
}
::WSACleanup();

用C++实现网络编程---抓取网络数据包的实现方法的更多相关文章

  1. Asp.net 使用正则和网络编程抓取网页数据(有用)

    Asp.net 使用正则和网络编程抓取网页数据(有用) Asp.net 使用正则和网络编程抓取网页数据(有用) /// <summary> /// 抓取网页对应内容 /// </su ...

  2. Fiddler:在PC和移动设备上抓取HTTPS数据包

    Fiddler是一个免费的Web调试代理,支持任何浏览器.系统以及平台.这个工具是进行Web和App网络开发的必备工具,戳此处下载. 根据Fiddler官网的描述,具有以下六大特点: Web调试 性能 ...

  3. 图解Fiddler如何抓取Android数据包

    介绍Fiddler抓取Android数据包希望对大家的工作和学习有所帮助! 电脑开启wifi热点 首先在电脑上下载一个wifi软件,我这里用的是猎豹wifi,电脑开启wifi热点后,如下图所示:  设 ...

  4. Fiddler基础用法-抓取浏览器数据包

    Fiddler基础知识 Fiddler是强大的抓包工具,它的原理是以web代理服务器的形式进行工作的,使用的代理地址是:127.0.0.1,端口默认为8888,我们也可以通过设置进行修改. 代理就是在 ...

  5. wireshark抓取OpenFlow数据包

    在写SDN控制器应用或者改写控制器源码的时候,经常需要抓包,验证网络功能,以及流表的执行结果等等,wireshark是个很好的抓包分析包的网络工具,下面简介如何用wireshark软件抓取OpenFl ...

  6. Wireshark学习笔记——怎样高速抓取HTTP数据包

    0.前言     在火狐浏览器和谷歌浏览器中能够很方便的调试network(抓取HTTP数据包),可是在360系列浏览器(兼容模式或IE标准模式)中抓取HTTP数据包就不那么那么方便了.尽管也可使用H ...

  7. Charles + Android 抓取Https数据包 (适用于Android 6.0及以下)

    通过Charles代理,我们能很轻易的抓取手机的Http请求,因为Http属于明文传输,所以我们能直接获取到我们要抓取的内容.但是Https内容本身就是加密的,这时我们会发现内容是加密的了.本文我们来 ...

  8. Charles 如何抓取https数据包

    Charles可以正常抓取http数据包,但是如果没有经过进一步设置的话,无法正常抓取https的数据包,通常会出现乱码.举个例子,如果没有做更多设置,Charles抓取https://www.bai ...

  9. Fiddler抓取https数据包

    Wireshark和Fiddler的优缺点: ①Wireshark是一种在网络层上工作的抓包工具,不仅自带大量的协议分析器,而且可以通过编写Wireshark插件来识别自定义的协议.虽然Wiresha ...

随机推荐

  1. [Android实例] Scroll原理-附ScrollView源码分析

    想象一下你拿着放大镜贴很近的看一副巨大的清明上河图, 那放大镜里可以看到的内容是很有限的, 而随着放大镜的上下左右移动,就可以看到不同的内容了 android中手机屏幕就相当于这个放大镜, 而看到的内 ...

  2. Synchronizing with Remote Repositories

    Synchronizing the states of local and remote repositories consists of pulling from and pushing to th ...

  3. RAC_Oracle集群服务安装前期准备Prepare(案例)

    2014-07-08 Created By BaoXinjian

  4. IGS_学习笔记06_IREP发布客户化集成接口为Web Service(案例)

    2015-01-03 Created By BaoXinjian

  5. C#(一维数组)

    定义数组 int[] 变量名 = new int [n]; 数组初始化 int[] myArray = new int[] {1, 3, 5, 7, 9}; //输入班级人数 //输入每个人的分数 / ...

  6. 用socket操作redis

    代码: $cmd = "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"; // set foo bar $socket = socke ...

  7. XHTML的使用规范

    一.XHTML的简介 XHTML指的是可扩展超文本标记语言 XHTML与HTML4.01几乎是相同的 XHTML是更严格更纯净的HTML版本 XHTML是以XML应用的方式定义的HTML 二.为什么使 ...

  8. 关于微信网页调用js-sdk相关接口注意事项目(一级域名与二级域名互相干扰!!!)

    不知道有没有网友遇到过同一个web应用用不同的域名(一级或二级域名)在两个公众号中调用JSSDK相关接口实现功能, 这种做法本来没有问题,问题在于用二级域名(同属一级域名下的二级域名)绑定另一个web ...

  9. php 获取时间今天明天昨天时间戳

    <?php echo "今天:".date("Y-m-d")."<br>";      echo "昨天:&qu ...

  10. Python中将打印输出导向日志文件

    a. 利用sys.stdout将print行导向到你定义的日志文件中,例如: import sys # make a copy of original stdout route stdout_back ...