Skip to content
freeman zhang edited this page Sep 16, 2016 · 4 revisions

设计思路

Sniffer的功能包括网络数据包的抓取、过滤和分析展示三个部分。

抓取是指(尽可能多地)获得网络上传输的数据包,这是Sniffer工作的基础。正常情况下,网络接口卡硬件(以下简称网卡)会处理本机发送以及发往本机的数据包。如果将网卡配置为混杂模式,我们可以获得经过本机但非发往本机的数据包,从而获得网络上更多的信息。不管网卡是不是工作在混杂模式,这个处理过程对普通用户是透明的。而我们希望通过Sniffer将这一过程尽可能详细地报告给普通用户。

由于单位时间内捕获的网络数据包可能非常多,Sniffer常常会设置一个功能对数据包进行过滤,从而让用户容易观察到自己感兴趣的数据包。例如,用户可以设置协议过滤器,只观察TCP协议的数据包;又或是设置地址过滤器,只观察特定主机发送的数据包。

分析展示是在完成上述工作之后,将捕获到的数据包和一些统计信息展示给Sniffer用户。数据包本身是二进制数据,可读性差。将其转换为结构化、人性化、用户友好的表现形式是非常重要的一个环节。

数据包抓取的三种不同实现方式:

Kernel Hacking实现方式

network-structure.png

上图是Linux操作系统内核inet子系统的架构示意图。inet子系统是Linux对于TCP/IP协议栈的一种实现。在子系统中,接收数据包的走向是从下往上传递的。发送与之相反。在不同层次的模块中,我们可以获取对应层次的数据包中的数据。但实际上,因为下层模块中的数据包是上层数据包的超集,所以我们只要尽可能在较低的层次获取数据包如,就可以获得一个数据包所有的信息(以太网帧头、网络层帧头、传输层帧头、应用数据载荷)。同时需要注意的是,层次并不是越低越好,例如:如果实现在图中的Driver(驱动)层,那么我们就需要对不同硬件的不同驱动进行对应修改。

通过kernel hacking的方式获取数据包,可以保证抓包的通用性。因为操作系统对于网络有完整的访问权限,只要抓取的层次选择正确,就可以查看任意层次任意协议的数据包,甚至修改其中的数据。这些操作对用户层都是透明的。此外,kernel hacking方式抓包的思路十分简单。因为所有的数据包都暂存在一个缓冲区内,只要把这个缓冲区读取出来并进行类型转换,就可以方便抓取和分析数据包内容。

虽然kernel hacking方式思路直接,但内核编程本身是复杂的。相对于应用层编程,内核编程没有丰富的库程序的支持。内核编程抽象层次低,需要注意的细节较多,稍有不慎就可能造成内存访问错误或系统死锁。内核也缺乏高效的调试方法,使得问题的解决变得尤为复杂。同时内核编程有时也是危险的。不同于应用程序出错仅仅会导致自身程序奔溃,由于内核工作在计算机系统软件部分的底层,一旦编程过程引入错误,就会影响整个系统的稳定性,造成系统奔溃,甚至硬件损毁或数据丢失。

依赖libpcap库实现方式

 libpcap是一个unix和类unix系统下实现网络数据包抓取、过滤和分析的库程序。winpcap是针对微软的Windows操作系统的一个变种。libpcap实际上基于BPF(Berkeley Packet Filter,伯克利数据包过滤)。BPF是数据链路层的一个接口,应用软件通过该接口可以实现数据包的过滤和抓取,而不需要内核的支持。这类应用软件需要满足一定的规范——pcap。libpcap是pcap API的一种实现。有很多著名的抓包工具都使用了libpcap库或其变种,如wireshark和tcpdump工具。

 libpcap库的使用将会大大简化了抓包工具的编程。运用libpcap后,sniffer设计流程如下:

1)选定需要监听的网络接口 2)初始化libpcap 3)创建过滤规则 4)执行抓包循环 5)抓包结束后退出

libpcap是事件驱动的。在抓包循环中,每得到一个数据包,libpcap会调用一个实现设定的回调函数。我们可以在回到函数中对抓取的数据包做处理,例如对数据包按照不同协议类型做类型转换,完成后续的分析和展示过程。

使用raw socket的实现

libpcap是跨平台的库程序,经过一些小的修改和编译便可以在大部分主流的操作系统上使用。但考虑到一些嵌入式设备,如智能手机,可能需要独立、轻便的实现。这时我们就可以使用raw socket的方式从头实现嗅探程序。

raw_socket支持一种AF_PACKET的地址簇。这类地址簇可以让raw_socket有直接读取和发送以太网帧的功能。

在计算机软件行业,尤其是有优秀的开源环境支持下,重复制造车轮是不被鼓励的。但是本小组本着学习、探索的态度,决定不借助第三方抓包库,仅使用raw socket从头实现自己的嗅探软件——Shaniffer。Shaniffer项目地址为:

https://github.com/tianxianbaobao/shaniffer

实现细节

我们选择的实现在GNU/Linux平台之上。主要因为GNU/Linux上有丰富的优秀开源软件可以学习和使用。并且,受惠于该平台本身的开放性,我们的研究可以深入系统内部,并能够在必要时做出修改。

数据包抓取、分析功能的实现

shapture.png

整个shaniffer的工作流程如图中[A]所示。整个程序为一个循环,不断接收数据包,并针对数据包类型做出不同的输出处理。

shaniffer会在三个地方对数据包的类型进行判断,这三个地方分别对应网络层、传输层和应用层。图中B显示了网络层协议的甄别和传输层协议的甄别。在以太网头部中包含了一个域,通过不同的数字区别网络层协议类型。shaniffer目前支持ARP和IP两种网络层协议。观察TCP/IP的细腰结构,我们知道很多传输层协议都是建立在IP层之上的。IP头部也包含一个域来区分上层传输层的协议类型。shaniffer目前支持的传输层协议包括TCP、UDP、ICMP、IGMP和OSPF。

建立在TCP和UDP协议之上,存在很多的应用层协议。对于这些协议,我们没有办法从TCP头或UDP头部找到一个域进行区分。因此,shaniffer利用端口号约定和一些检查来猜测应用层的协议,如图中[C]所示。目前shaniffer支持的应用层协议包括:RIP、DHCP。不能被识别的应用层协议数据包,我们将它们作为普通的TCP、UDP数据包进行处理,即简单地使用十六进制格式输出的数据包内容。

对于具体的数据包,我们会输出每一层头部信息以及数据包载荷,并给出十六进制格式下的数据包内容(Dump)。图[D]展示了ARP包的处理流程。

shaniffer会对各类数据包进行统计。执行循环可以由用户发出的信号打断并退出程序。

数据包过滤功能的实现

数据包过滤功能由一段shell程序shailter提供。shailter提供了下层的raw过滤方式,可以筛选出包含查询字符串的数据包。shailter同时提供各类高级过滤,如过滤源/目的IP的数据包。这些高级过滤建立在raw方式之上,使用更加简便。但需要灵活查询时,也可以直接使用raw方式。

增加对某类型包的识别

Linux系统支持的协议数量非常庞大。在shaniffer中,我们没有一一实现对它们的识别。但是,shaniffer更像是一个框架,使得添加对某一类型包的识别变得非常简单。主要步骤包括:

  1. 如果增加的是对基于IP协议的数据包的识别 构造输出函数,在函数开头调用print_ip_header来输出IP头部信息(以及以太网头部) 修改ProcessIPPacket函数,增加统计和对输出函数的调用 如果增加的是对基于非IP协议数据包的识别 构造输出函数,在函数开头调用print_eth_header来输出以太网头部信息 修改ProcessPacket函数,增加统计和对输出函数的调用

  2. 在shailter中增加相应的过滤规则[可选]

下面以ARP类型包的识别为例,介绍如何增加对某类型包的识别: ARP包是一个非IP协议包,所以我们在ProcessPacket的函数中进行修改。表示以太网头的ethhdr数据结构包含h_proto域,用来指示数据包的协议类型。通过查询头文件定义,我们了解到0x0008为IP数据包,0x0608为arp包。所以我们增加h_proto=0x0608的case分支,用来输出ARP包的信息,同时增加全局变量arp统计ARP数据包的数量,并在接收到ARP包后进行自增。我们定义pring_arp_packet函数来完成输出ARP包信息的操作,具体内容可自行实现。

展示功能的实现

Qt介绍

Qt是Trolltech公司于1991年开发的一个一个跨平台的C++应用程序开发框架,广泛用于开发GUI。而且是自由开源的软件,在GNU(LGPL)条款下发布,支持广泛的编译器。使用Qt开发的软件,相同的代码可以在任何支持的平台上编译及运行,会自动依平台的不同,表现平台特有的图形界面风格。

Qt的图形界面基础是QWidget,Qt中所有组件如标签,GUI,工具,按钮都派生自QWidget,而QWidget本身是QObject的子类。QWidget负责接受鼠标,键盘,和来自窗口系统的其他事件,并描绘自身在屏幕上。每一个GUI组件都是一个widget,widget还可以作为容器,把其他的widget包含在内。Qt利用信号和槽(signals/slots)机制进行图形界面的异步通信,和传统的callback方式不同,当时间发生时,对象发送一个signal,然后该signal将被指定的slot捕获,而且传递的参数必须类型一致,这比callback调用形式更加安全,耦合性更低。

设计

既然功能复杂,效果酷炫,体积巨大的wireshark已经存在,那么我们的设计方针就是轻量级,界面清爽,体积小巧,用最少的GUI元素来实现一个抓包工具的核心内容:1 选择适配器,2 包过滤器,3 数据包的详略显示。那么只需要3组对应的GUI组件就能完成其核心功能。利用Qt designer快速设计一个界面如下:

Screenshotfrom2016-06-1419-57-46.png

其用到的GUI组件从左到右,从上到下依次是,LineEdit,Button,LineEdit,Button,TableWidget,TextBrowser。

实现

接下来把这个GUI的壳和原来的核心(Core)连接起来,为了同时运行包捕获任务,以及响应用户操作的任务,需要利用pthread来实现并发。用户一共允许有3个操作:1 点击 on(off) 按钮, 2 点击filter按钮,3 点击一个被捕获的包以获得详细信息。

1 点击on(off)按钮, 将程序运行状态取反,并根据运行状态改变按钮文本(on->off;off->on),将按钮左边相邻的LineEdit的内容取出,以signal的形式发送给Core线程,Core线程的Slot捕获到该Signal之后,若当前Core线程没有开启,就开启一个包捕获thread监听指定的适配器,否则结束当前正在运行的包捕获线程

2 点击filter按钮,将该按钮的左边取出,左边的过滤规则按照等式形式编写,不同的条件用 '|' 隔开,比如src_ip=xx.xx.xx.xx | proto=udp,这将只显示源IP为xx.xx.xx.xx且协议类型为UDP的数据包。同样点击按钮会发出一个signal,被捕获后执行过滤函数。

3 点击在TableWidget中显示的一个被捕获的包,将获取它的编号,然后将该包的详细内容显示在最下方的TextBrowser。这同样会产生一个Signal,然后被相应的Slot捕获执行。

另外,Core thread将捕获的数据包同样以Singal的方式不断的发送到GUI treading中保存起来,数据包以"摘要-内容"的形式存放在内存中,默认只在TableWidget中显示摘要,被点击后在TextBrowser中显示具体内容,以上操作如下所示:

Screenshotfrom2016-06-1420-21-57.png

局限性

只能捕获以太网数据包。

使用

获取、编译和安装:

git clone https://github.com/freemandealer/shaniffer.git
make install     # 需提供root权限

抓包:

shapture

如果想抓去某一网络接口的包,可以制定接口设备: shapture wlan0 # 仅捕获无线接口wlan0上的包

停止抓包:

ctrl + c

对数据包进行分析及过滤:

抓取的包信息全部存放在/var/shanilog.txt文件中。这个文件是人类可读的。但考虑到数据包数量庞大,我们提供了过滤器来辅助定位数据包:

shailter srcip 192.168.1.1 # 过滤出源IP地址为 192.168.1.1 的包
shailter dstmac FF-FF-FF-FF-FF-FF # 过滤出目的MAC地址为FF-FF-FF-FF-FF-FF的数据包
FF-FF-FF-FF-FF-FF
shailter raw "secret" # 过滤出包含字符串“secret”的数据包

设计实现参考

WITHOUT libpcap http://www.binarytides.com/packet-sniffer-code-c-linux/ WITH/WITHOUT libpcap https://www.quora.com/How-do-we-implement-a-network-packet-sniffer [1]WITH http://www.tcpdump.org/pcap.htm sample code http://www.tcpdump.org/sniffex.c WITH http://yuba.stanford.edu/~casado/pcap/section1.html detailed) WITH http://opensourceforu.efytimes.com/2011/02/capturing-packets-c-program-libpcap/ (detailed) WITHOUT http://opensourceforu.efytimes.com/2015/03/a-guide-to-using-raw-sockets/ (raw socket) libpcap source https://github.com/the-tcpdump-group/libpcap why PF_PACKET can work? http://stackoverflow.com/questions/26212014/create-a-layer-2-ethernet-socket-with-boost-asio-raw-socket-in-c http://www.microhowto.info/howto/send_an_arbitrary_ethernet_frame_using_an_af_packet_socket_in_c.html and more ..

Qt 介绍 https://zh.wikipedia.org/wiki/Qt

Wireshark使用参考

BPF https://en.wikipedia.org/wiki/Berkeley_Packet_Filter http://blog.jobbole.com/70907/ wireshark文档 https://www.wireshark.org/docs/wsug_html/