WinpCap是在Windows平台下的一个网络数据操作集成模块(Pcap的Windows版本),如果你不知道WinpCap这玩意儿的话,那么Wireshark你总应该不会太陌生(Wireshark抓包真心不要太舒服,而其抓包功能则来自于WinpCap。)。在Windows系统下除了TCP/UDP数据包以外如果需要发送RAW SOCKET的话是一件比较麻烦的事情,尤其是在自己构造二层协议之类的情况,在没有驱动的情况下似乎比较困难。而在这种情况下WinpCap本身集成了比较多的网络数据包的功能(比如抓包、发送自定义数据包等等),因此WinpCap也就成了一个比较好的选择。
那么说了一段废话,这边文章的主要目的只是简单介绍一个流程,以及如何使用C/C++在Windows下利用Winpcap来进行网络数据编程。
首先介绍一下抓包-》发包的基本流程:
获取网卡-》选择并打开需要的网卡-》设置封包过滤条件-》loop数据包事件,在loop中处理抓获的数据包。
0.预先准备
为了进行编译,你需要从winpcap官网下载SDK,然后将解压获得的头文件和lib文件导入到你的项目中。
1.获取网卡
在WinpCap的API中提供了 一个名为 pcap_findalldevs 的函数来获取当前计算机的所有可用网卡(包括虚拟网卡以及系统环回),其原型为:
int pcap_findalldevs (pcap_if_t **alldevsp, char *errbuf)
此函数用于构建一个可用网卡列表,这些“网卡”将在后续被用于pcap_open_live函数(打开网卡)。其第一个参数 alldevsp 用于存放返回的网卡列表的指针,errbuf为错误信息的内容。
** alldevsp 为一个指向指针的指针,因此实际上这里需要传递的是一个 pcap_if_t * 变量。有关指针的问题你需要好好理解下C语言。
那么,举个栗子:
pcap_if_t *alldevs; // 用于存放设备列表的指针变量。 char errbuf[PCAP_ERRBUF_SIZE]; // PCAP_ERRBUF_SIZE是PCAP SDK已定义的 if (pcap_findalldevs(&alldevs, errbuf) < 0){ return NULL; } //go what you want to do
如果函数返回值为-1表示失败,此时errbuf会有对应提示信息。0表示获取成功,此时alldevs指向的就是以获取到的设备列表
2.我有哪些网卡呢?
上一步中你已经获取到了你电脑里面的网卡了,然而,我TM怎么知道这些网卡是什么呢?这些“网卡”有哪些特征可以用?
你还记得上一步中获取到的pcap_if_t的那个指针嘛?我们来看下定义就知道了
struct pcap_if { struct pcap_if *next; char *name; /* name to hand to "pcap_open_live()" */ char *description; /* textual description of interface, or NULL */ struct pcap_addr *addresses; bpf_u_int32 flags; /* PCAP_IF_ interface flags */ }; typedef struct pcap_if pcap_if_t;
所以如上图所示,获取到的信息为一个链表结构,每一个节点包含网卡名称、描述、pcap_addr(网卡地址),以及接口标记。
遍历链表应该还是比较好说的:
pcap_if_t *d; int i = 0; for (d = alldevs; d; d = d->next){ printf("%d . %s (%s)\n" , i++ , d->name , d->description); }
3.打开我选择的网卡
在pcap api中,我们使用pcap_open_live函数来打开一个网卡。在第二步中你应该知道了如何去遍历获取到的网卡列表,那么这一步老子要打开网卡了,问题是我需要啥才能打开?请看函数原型:
pcap_t* pcap_open_live ( const char * device, int snaplen, int promisc, int to_ms, char * ebuf )
按照官方文档的说法:“pcap_open_live用于获取抓包描述符,以抓取在这个网络上所发送/接受的数据包。device参数为设备名称(即*pcap_if_t -> name),snaplen指定了抓获数据包的最大是多少个字节,如果你所设置的snaplen小于所抓到的包的长度(比如你设置了1000,但是抓到了一个1200字节的数据包),则只会返回该数据包的前snaplen个字节的数据。promisc指定是否以“混乱模式”。to_ms指定读取超时的时间(看文档的意思应该是,当抓到一个数据包以后,在指定时间内不马上返回数据包,而是等待相应时间后把后续抓到的数据包也一起返回,如果设置为0会导致无期限的等待,同时会返回错误。)”
这个函数在操作成功后返回实际的pcap_t实例,代表这块网卡。
(话说我现在写这个才发现我之前模拟协议怎么那么慢。。。妈的to_ms设置的1000,难怪慢的要死)
所以参数介绍完了以后,来,怎么调用。
之前枚举网卡做了是吧?可以获取到name撒,把你要的网卡的name取出来,然后丢到device参数
snaplen默认65535就好啦,除非你的数据包长度有限
promisc这东西不知道干嘛的,反正我给的1,各位欢迎拍砖
to_ms这东西。。。。。仁者见仁智者见智,如果对延迟要求比较高的话,建议设置50以内(单位是ms),如果希望能一次处理多个数据的话,可以稍微设置的大一点。
ebuf我就不解释了嘛。。。。
那么最后的调用就成了:
pcap_t * netcard = NULL; if ((netcard = pcap_open_live(d->name, 65536, 1, 10, errbuf)) == NULL){ log("WARNING : Handle Failed"); return 1; } //continue
4.设置封包过滤条件
网卡已经打开了,接下来做啥?
你不是要抓包吗,抓包要有指定的对象嘛,比如说我要抓pppoe的,或者我要抓udp的,对不对,那么我就应该设置条件。那么这一步就是设置条件咯。
以前用wireshark的时候直接输入条件就行,比如”ppp || pppoes”,然而对于计算机来说他根本不知道你说的什么鸟语,就和你写了C语言但是不编译电脑不知道怎么做一样,所以pcap里面提供一个compile函数来将你指定的条件转换为“binary code”(其实应该不是这个吧。。。他的实际类型是bpf_program)
使用pcap_compile来编译你的过滤条件,然后再使用pcap_setfilter使其生效。对于pcap_compile函数,你需要提供上一步中返回的netcard,一个bpf_program实例的指针,你设置的过滤条件(比如”ppp || pppoes”),再加上。。。之前那个网卡的地址。
一个栗子如下:
bpf_program fcode; //下面的 d 为第二步中你所选中的网卡的结构体指针 if (pcap_compile(netcard, &fcode, "pppoed || pppoes", 1, d->addresses ? ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr : 0xffffff) >= 0){ //go continue }else{ //go error }
5.应用过滤,设置回调函数,执行循环
到了这一步,我们已经做了
- 枚举网卡
- 选中网卡
- 打开网卡
- 编译过滤条件
那么接下来我们要做的,首先把编译好的过滤条件应用上去,并且设置好捕捉到数据包时的回调函数以进行处理,然后启动loop开始抓包。
if (pcap_setfilter(netcard, &fcode) >= 0){ //开始loop,使用的参数为 netcard , -1 (这里负数表示永久loop) , packet_handler为回掉函数的名称,最后一个参数我这里设置为NULL //-1 is returned on an error; 0 is returned if cnt is exhausted; -2 is returned if the loop terminated due to a call to pcap_breakloop() before any packets were processed. pcap_loop(netcard, -1, packet_handler, NULL); log("INFO : loop quit!"); } else{ log("WARNING : Set Filter Error"); }
而对应的回掉函数 packet_handler,其原型应当为:
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) //header包含了时间戳以及数据包长度 //pkt_data则为原始的数据包内容
想问下这个软件是如何实现的?http://www.netresec.com/?page=RawCap。这个软件是没有依赖winpcac的。可以抓本地回环包