日期:2014-05-16 浏览次数:20682 次
硬件有了,先驱起来再说,完事之后就要想办法抓包了,千万别以为抓包很容易,一个软件就搞定,想抓取802.11的数据包,还真得下一番功夫啊,本文接下来的部分就谈谈我的802.11抓包经历。
Linux内核的抓包机制软件上全在ptype_all这个链表,而不存在所谓“网卡混杂模式”,混杂模式主要是硬件上的概念,有些网卡会在芯片内部完成MAC地址的过滤,因此必须让芯片“知道”不要过滤任何地址这一件事,因此就有了混杂模式的概念,对于无线网络,混杂模式在理解上更加复杂,因此“不要过滤任何数据帧”和802.11规定的AP地址过滤某些行为上是矛盾的。举个例子,一个没有和此AP建立关联的移动节点发来的包,AP按照802.11规范是要丢掉它的,然而混杂模式又要求接受它,这就是矛盾。矛盾的本质原因在于无线链路的“无边界”特征,这是电磁波的物理特性导致的。对于此谁也无能改变,因此ESS无线网络就不能设计成一个完全的广播网络,否则一个三维空间的广播造成的冲突要浪费多少资源啊--我们知道有线局域网以太网最初是一个一维线缆上的广播而已,现在有了交换机,CSMA/CD基本已经不再被使用了。只能由AP接入点来负责在移动节点之间转发数据帧,因为只有它知道谁跟自己建立了关联。这种区别造成了无线网络抓包的尴尬。
在设计上,操作系统完全避免了这个尴尬,要么它根本不允许在802.11这个层次上进行抓包,要么由驱动决定如何实现抓包。对于Linux,高版本内核实现了统一的802.11适配层框架,驱动的实现可选使用,低版本内核完全由驱动来决定能否实现抓包;对于Windows,如果你使用WireShark工具在无线网卡上进行抓包,会得到以下错误:在Wireshark的网站上,也有类似的说法:
这个may not未免太无耻了。然而tcpdump的手册上却说可以在monitor mode下抓取802.11帧,然而这却需要驱动来支持。对Linux而言,虽然较高版本的内核支持了802.11适配层,然而却不是每个驱动的实现都遵循了这个框架,比如我手上的ralink的驱动就很扯,完全按照windows的那一套来写的。因此依靠monitor mode来抓取802.11帧这完全靠不住!接下来分析Linux的抓包机制和802.11适配层的关系以及衔接。
对于进入的包,包在netif_receive_skb中被截取,对于出去的包,则在dev_queue_xmit,而这两个地方全部都在802.11逻辑的上面,这就涉及到了802.11的设计。这两点看上去是矛盾的,其实不然。有两种方案可实现,其一就是将以太帧封装在802.11帧里面,类似隧道那样,其二就是直接通过以太帧构造802.11帧,两种帧只要能保证可以互相生成即可。802.11采用的正是这种方式,数据帧发往无线链路时,由以太帧生成802.11帧,反过来由802.11帧生成以太帧。
这就需要做一个适配层,用来转换两种帧,在linux中,这种适配层表述的很清晰。基于2.6.32的内核,$SRC/driver/net/mac80211目录下面的代码就是适配层的实现。值得注意的是,旧一点的内核版本虽然没有这么清晰的目录结构,代码也是很明白的,总的实现如下图所示:
注意,适配器适配的两种帧并不是平行的,在协议栈上是上下的关系,这是因为我们已经习惯了以太网,因此需要将802.11适配到以太网,而不需要反过来适配,因此对外只需要呈现一个以太网卡接口即可,没有必要再显示一个“无线网卡设备”接口。最终的结果就是802.11和802.3更像是协议栈上的上下层关系,而不是两种平行的协议。
由于802.11的处理逻辑完全在“网卡设备”接口以下,而抓包逻辑则在“网卡设备”接口以上(netif_XX/dev_YY),而Linux内核看到实现了802.11的网卡设备时,所有路径均已经经过了适配层,因此抓包逻辑看到的“无线网卡”上的流量就都成了以太帧了。另外对于实现了基础结构BSS无线AP的无线网卡而言,有时它转发的是两个无线站点之间的流量,为了使“网卡设备”接口这种流量,需要将所有的802.11数据帧在抉择是发往有线网还是发给另一个无线站点之前全部进行适配,适配成以太帧,如下图所示:
这样,我们知道了为何无法使用tcpdump抓取802.11帧了,接下来就是想办法抓取到这种帧。最关键的事情就是找到在哪里802.11帧转化为了以太帧以及相反的转化,而这很容易,在2.6.32内核中,ieee80211_invoke_rx_handlers函数里面取到的就是802.11帧,不过注意,一定要在__ieee80211_data_to_8023调用之前,对于其它的内核版本,也是类似的。找到了这个之后,接下来需要将以下的代码添加到你找到的位置:
list_for_each_entry_rcu(ptype, &ptype_all, list) { if (ptype->dev == null_or_orig || ptype->dev == skb->dev || ptype->dev == orig_dev) ret = deliver_skb(skb, ptype, skb->dev); } }
list_for_each_entry_rcu(sdata, &local->interfaces, list) { if (sdata->vif.type != NL80211_IFTYPE_MONITOR) continue; if (sdata->u.mntr_f