请选择 进入手机版 | 继续访问电脑版

Bacysoft.cn

 找回密码
 邀请注册
查看: 12735|回复: 0

网游辅助(外挂)程序开发教程及实例分析02 - WinPcap 编程

[复制链接]
发表于 2014-9-3 21:33:08 | 显示全部楼层 |阅读模式
关键字:网游辅助(外挂) VS2010 WinPcap

WinPcap 原理
我们在安装 WinPcap 的时候,除了安装相关的 Dll 文件到系统目录,最重要的是在系统的 NDIS 层安装了一个用于原始封包过滤的驱动程序。通过 Dll 提供的函数,使得运行于 Ring3 层的应用程序也能访问到底层的网络数据。至于这些过程具体是如何实现的,不是本教程关注的内容,有兴趣的读者可以自行百度,或者直接阅读 WinPcap 的源码!有任何心得体会欢迎与我交流:)

WinPcap 编程的一般方法
平心而论,使用 WinPcap 提供的 API 接口编程实现原始封包数据的获取、发送、转储等功能,还是比较简单的。以抓包为例,必须步骤依次是:获取网络驱动接口、打开网络驱动接口、开始抓包,通常我们不会需要捕获所有的数据包,因此抓包之前还需要设定合适的过滤规则,比如指定 IP 地址、TCP 或者 UDP 端口等等。下面将分别介绍这些步骤。

1、获取网络驱动接口
需要注意的是,我们这里说的是网络驱动接口,而不是物理网卡,也就是说实际上 WinPcap 是通过网卡驱动程序来实现与网卡的交互。这也就是为什么在很多机器上明明只有一个物理网卡,但 WinPcap 却能获取到两个或者更多的网卡驱动接口信息。那些软件虚拟的网卡多数是因为系统中安装了与 VPN 客户端类似的连接程序。下面是获取网络驱动接口信息的代码:
  1.                 pcap_if_t *alldevs, *pDev;
  2.                 char errbuf[PCAP_ERRBUF_SIZE+1];

  3.                 /* Retrieve the device list */
  4.                 if(pcap_findalldevs(&alldevs, errbuf) == -1)
  5.                 {
  6.                         //出错了返回自定义错误代码‘2’,通常用于定位故障;
  7.                         return 2;
  8.                 }
复制代码
函数 pcap_findalldevs 用于获取网络驱动接口的相关信息,并将他们存储在由 alldevs 指向的链表结构中。该链表结构的元素均由 pcap_if_t  结构定义,其中最重要的字段就是接口的名字,在以上代码中可以使用 pDev->name 访问,比如笔者机器上的物理网卡的 name 就是:
  1. \Device\NPF_{79D843D7-2AE1-4E2E-A54F-D46154627742}
复制代码
这个名字不是很友好,看起来应该就是系统注册表中对应的表项值。pDev->description 中是此接口的描述信息:
  1. Network adapter 'Realtek 10/100/1000 Ethernet NIC
复制代码
这个就友好多了吧。除了名字和描述,pDev->address 也很重要,主要保存了配置在该接口上的网络地址,通常就是 IP 地址和对应的掩码。需要注意的是,一个网络接口上通常可以配置多个地址,因此 address 字段指向的是一个地址链表。

2、选择合适的网络驱动接口(最佳路由匹配)
如果上一步仅获取到一个接口,那就直接用它好了,但是实际的情况是很多时候你都会获取到两个以及两个以上的接口。比如笔记本电脑通常会有有线和无线两种网卡,自然对应两个网络驱动接口。那么这种情况下如何选择合适的网卡呢?通常为了简化程序设计,可以直接将列出所有的接口信息,让用户手动选择!但是本教程采用最佳路由匹配的方法让程序自动选择合适的接口。

要使用最佳路由匹配,需要在我们的程序中包含 Iphlpapi.h 头文件,这是标准的 Windows API,因此可以使用如下代码:
  1. #include <Iphlpapi.h>
复制代码
所谓最佳路由,其实就是找网关,方法是调用函数 GetBestRoute 计算出到达某个公网地址“114.114.114.114”的下一跳地址,通常就是网卡上配置的网关地址。接着逐个计算网络驱动接口上的 IP 是否与下一跳地址在相同网段,如果确实在相同网段,那么则拥有该 IP 的接口就是我们要使用的接口。关于 IP 路由的基础知识,如果您不是很了解的话,可以参考本站教程:[图解] IP 路由基础。通过最佳路由选定网络接口(网卡)的代码如下:
  1.                 PMIB_IPFORWARDROW pBestRoute = new MIB_IPFORWARDROW;
  2.                 DWORD dwGateway,*pIPAddr,*pNetMask;
  3.                 CString str;

  4.                 if(GetBestRoute(inet_addr("114.114.114.114"),0,pBestRoute) == NO_ERROR)
  5.                 {
  6.                         dwGateway = pBestRoute->dwForwardNextHop;
  7.                 }

  8.                 pcap_addr_t *a;

  9.                 for(pDev=alldevs;pDev;pDev=pDev->next)
  10.                 {
  11.                         for(a=pDev->addresses;a;a=a->next)
  12.                         {
  13.                                 if(a->addr->sa_family == AF_INET)
  14.                                 {
  15.                                         pIPAddr = (DWORD*)(&a->addr->sa_data[2]);
  16.                                         pNetMask = (DWORD*)(&a->netmask->sa_data[2]);
  17.                                         if((*pIPAddr & *pNetMask) == (dwGateway & *pNetMask))
  18.                                         {
  19.                                                 str.Format("%s",pDev->name);
  20.                                                 pcap_freealldevs(alldevs);
  21.                                                 delete pBestRoute;
  22.                                         }
  23.                                 }
  24.                         }
  25. }
复制代码
这段代码首先使用函数 GetBestRoute 获取到达指定地址“114.114.114.114”的最佳路由信息,并将其存储在由 pBestRoute 指向的 MIB_IPFORWARDROW 结构中,MIB_IPFORWARDROW 结构中的字段 dwForwardNextHop 就是我们所需要的下一跳地址,多数情况下这就是网关地址!然后接下来的循环体用来找出与下一跳地址在相同网段的接口 IP,外循环是遍历网络驱动接口,内循环是遍历当前接口上的 IP 地址。判断接口 IP 与下一跳地址是否在相同网段的方法是:“接口IP & 接口掩码” 等于 “下一跳(网关)地址 & 接口掩码”,如果找到了符合条件的 IP ,则将拥有这个 IP 地址的网络驱动接口的名字保存起来,然后使用函数 pcap_freealldevs 释放之前由 pcap_findalldevs 获取的接口列表 alldevs。

注意:此方法并不能适用于系统中存在逻辑连接的情况!什么是逻辑连接?很多计算机上网都是通过 PPPoE 宽带拨号上网的,拨号成功以后系统中将存在一条逻辑连接,它是建立在网络驱动接口之上的虚拟连接,而 pcap_findalldevs 函数无法获取到这些逻辑连接的信息。同时 WinXP 系统下没有很好的办法可以获取逻辑连接与网络驱动接口直接的堆叠关系,如果是 Win7 或者以上系统则可以通过 GetIfStackTable 函数来获取逻辑连接与物理连接之间的堆叠关系。

3、打开网络驱动接口
当选定了正确的网络驱动接口后,使用函数 pcap_open 来打开网络驱动接口,如果成功将返回一个对应网络驱动接口的句柄,并保存在 adhandle 中,以后如果需要对该网络驱动接口进行相关操作,那么都可以通过 adhandle 来实现。
  1. if((adhandle = pcap_open(pDev->name, 65536, 0, 250, NULL, errbuf)) == NULL)[indent]{
  2.         // 打开失败!
  3.         return 0;
  4. }
复制代码
仅打开接口,创建一个接口句柄还不能满足我们的要求,必须要能针对指定的封包流(只对游戏封包感兴趣)进行过滤,一来降低系统消耗,二来减少不必要的数据处理。因此我们还需要设定过滤规则,并将过滤规则与网络驱动接口绑定。相关代码如下:
  1. if(pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) < 0)
  2. {
  3.         //过滤规则编译失败!
  4.         return 0;
  5. }
  6. if (pcap_setfilter(adhandle, &fcode)<0)[/indent]{
  7.         //设定过滤规则失败!
  8.         return 0;
  9. }
复制代码
代码中的 fcode 是由函数 pcap_compile 输出的过滤规则,根据 WinPcap 手册我们知道 fcode 中保存的是二进制的可执行代码,可以通过函数 pcap_setfilter 将这段代码与对应的网络驱动接口绑定起来,以实现对指定封包数据流的捕获。packet_filter  则是用用户指定的用于描述封包数据流的规则,也就是通常所说的过滤规则。fcode packet_filter 可以通过如下代码定义:
  1.         char *packet_filter = "tcp port 4000";
  2.         struct bpf_program fcode;
复制代码
封包过滤规则详见 WinPcap 手册中的“Filtering expression syntax”小节。

4、开始抓包
在以上步骤都顺利完成后,终于可以开始捕获我们心仪已久网络封包数据了!在 WinPcap 中提供了三种获取封包的方式,分别是:pcap_loop、pcap_dispatch 和 pcap_next(pcap_next_ex),如何选择呢?

首先 pcap_next 不支持回调函数,大大降低灵活性,直接出局!pcap_loop 貌似没有 pcap_dispatch 灵活,介于 WinPcap 对 pcap_loop 与 pcap_dispatch 区别的介绍不够详细,我也不好妄自揣测。本文选择使用 pcap_dispatch 的原因是因为大名鼎鼎的 Wireshark 就是用的 pcap_dispatch ,有兴趣的朋友可以去读读 Wireshark 的源码(dumpcap.c)。开启抓包的代码如下:
  1. pcap_dispatch(adhandle, 1, packet_handler, (u_char*)pParam);

  2. void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
  3. {//...处理封包的代码...}
复制代码
代码中的 packet_handler 是回调函数,每当 pcap_dispatch 获取到一个符合过滤规则的封包后,就会自动调用 packet_handler  来对封包中的数据进行处理,回调函数有三个参数,第一个参数可以用来给回调函数传递参数,比如告诉回调函数应该如何处理数据;第二个参数是 WinPcap 自动生成的关于这个数据包的一些基本信息,比如获取此封包的时间,封包长度等等;第三个参数就是原始的封包数据了,包含了从数据链路层到应用层的完整封包数据。

到此,使用 WinPcap 进行抓包的主要步骤就介绍完了。如果读者觉得无法很好的理解这部分内容,建议先去学习下 WinPcap技术手册,并动手编译调试一下 WinPcap 源码包中 Example 目录下的实例,以加深理解。
您需要登录后才可以回帖 登录 | 邀请注册

本版积分规则



阿里云|腾讯云|联系方式|Bacysoft.cn ( 京ICP备08000958号-1 )

GMT+8, 2019-10-19 10:17 , Processed in 0.018362 second(s), 21 queries , Gzip On.

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表