学习笔记6

来源:互联网 发布:幼儿园课件制作软件 编辑:程序博客网 时间:2024/04/29 08:24

##########################################################################
2014-01-21
libpcap(Packet Capture Library),数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库。独立于系统的用户层包捕获的API接口,为底层网络监测提供了一个可移植的框架。

工作原理:
主要由网络分接头(Network Tap)和数据过滤器(Packet Filter)组成。网络分接头能将网卡驱动接收到的数据做一个拷贝,再递交到数据过滤器,数据过滤器根据BSD Packet Filter(BPF)算法对网卡接收到的链路层数据包进行过滤。而BPF算法的基本思想是根据用户定义的规则决定是否接收此数据包以及需要拷贝该数据包的哪些内容,然后将过滤后的数据给与过滤器相关联的上层应用程序。
libpcap的包捕获机制就是在数据链路层加一个旁路处理。libpcap利用已经创建的Socket从链路层驱动程序获得数据包的拷贝,再通过Tap函数将数据包发给BPF过滤器。BPF过滤器根据用户已经定义好的过滤规则对数据包进行逐一匹配,匹配成功则放入内核缓冲区,并传递给用户缓冲区,匹配失败则直接丢弃。如果没有设置过滤规则,所有的数据包都将视作匹配成功处理。

抓包框架:
pcap_lookupdev()函数用于查找网络设备,返回可被pcap_open_live()函数调用的网络设备名指针。
pcap_open_live()函数用于打开网络设备,并且返回用于捕获网络数据包的数据包捕获描述字。对于此设备的操作都要基于此网络设备描述字。
pcap_lookupnet()函数用于获得指定网络设备的网络号和掩码。
pcap_compile()函数用于将用户制定的过滤策略编译到过滤程序中。
pcap_setfilter()函数用于设置过滤器。
pcap_loop()函数和pcap_dispatch()函数用于捕获数据包,捕获后还可以进行处理,此处pcap_next()和pcap_next_ex()两个函数也可以用来捕获数据包。
pcap_close()函数用于关闭网络设备,释放资源。

pcap应用程序格式:
1、决定需要嗅探的接口;
2、初始化pcap;
3、如有需要嗅探特定的传输(如TCP/IP包,发往端口23的包等),可以创建一个规则集合并编译和使用它;
4、进入pcap的主体循环,在这个过程中对接收到的包进行处理;
5、嗅探结束后,关闭会话并结束。

##########################################################################
2014-01-22
详细步骤:
1、设置想要嗅探的设备
(1)通过命令行参数由用户指定:
#include <stdio.h>
#include <pcap.h>
int main(int argc, char *argv[])
{
   char *dev = argv[1];
   printf("Device: %s", dev);
   return(0);
}
(2)通过pcap_lookupdev()函数获取:
#include <stdio.h>
#include <pcap.h>
int main()
{
   char *dev, errbuf[PCAP_ERRBUF_SIZE];
   dev = pcap_lookupdev(errbuf);
   printf("Device: %s", dev);
   return(0);
}

2、打开设备进行嗅探
pacp_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
第一个参数device是由骤1获得的设备名指针;第二个参数snaplen定义了捕获的最大字节数;第三个参数promisc设置是否为混杂模式;第四个参数to_ms指定了读取时的超时值,单位是毫秒,0表示一直嗅探直到错误发生,-1表示不确定;最后一个参数ebuf是一个可以存入任何错误信息的字符串。此函数返回会话句柄。

3、过滤过信
实现这一过程由pcap_compile()与pcap_setfilter()两个函数完成,在使用过滤器前要编译它。
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
第一个参数是会话句柄,第二个参数是存储被编译表达式版本的地址引用,第三个参数是存储在规定字符串格式里的被编译表达式本身,第四个参数定义表达式是否被优化(0为false,1为true),最后一个参数指定应用此过滤器的网络掩码。函数返回-1为失败,其他的任何值都表明是成功的。表达式被编译之后就可以使用了。
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
第一个参数是会话句柄,第二个参数是被编译表达式版本的引用。

示例:
   #include <pcap.h>
   pcap_t *handle; /* 会话的句柄 */
   char dev[] = "eth0"; /* 执行嗅探的设备 */
   char errbuf[PCAP_ERRBUF_SIZE]; /* 存储错误 信息的字符串 */
   struct bpf_program filter; /*已经编译好的过滤表达式*/
   char filter_app[] = "port 23"; /* 过滤表达式*/
   bpf_u_int32 mask; /* 执行嗅探的设备的网络掩码 */
   bpf_u_int32 net; /* 执行嗅探的设备的IP地址 */
   pcap_lookupnet(dev, &net, &mask, errbuf);
   handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
   pcap_compile(handle, &filter, filter_app, 0, net);
   pcap_setfilter(handle, &filter);
这个程序使嗅探器嗅探经由端口23的所有通信,使用混杂模式,设备是eth0。

##########################################################################
2014-01-23
4、实际嗅探
实际嗅探有两种手段。可以一次只捕捉一个包,也可以进入一个循环,等捕捉到多个包再进行处理。
pcap_next函数
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
第一个参数是会话句柄,第二个参数是指向一个包括了当前数据包总体信息(被捕捉的时间,包的长度,其被指定的部分长度)的结构体的指针。pcap_next()返回一个u_char指针给被这个结构体描述的包。
这里有一个演示怎样使用pcap_next()来嗅探一个包的例子:
   #include <pcap.h>
   #include <stdio.h>
   int main()
   {
   pcap_t *handle; /* 会话句柄 */
   char *dev; /* 执行嗅探的设备 */
   char errbuf[PCAP_ERRBUF_SIZE]; /* 存储错误信息的字符串 */
  
   struct bpf_program filter; /* 已经编译好的过滤器 */
   char filter_app[] = "port 23"; /* 过滤表达式 */
   bpf_u_int32 mask; /* 所在网络的掩码 */
   bpf_u_int32 net; /* 主机的IP地址 */
   struct pcap_pkthdr header; /* 由pcap.h定义 */
   const u_char *packet; /* 实际的包 */
   /* Define the device */
   dev = pcap_lookupdev(errbuf);
   /* 探查设备属性 */
   pcap_lookupnet(dev, &net, &mask, errbuf);
   /* 以混杂模式打开会话 */
   handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
   /* 编译并应用过滤器 */
   pcap_compile(handle, &filter, filter_app, 0, net);
   pcap_setfilter(handle, &filter);
   /* 截获一个包 */
   packet = pcap_next(handle, &header);
   /* 打印它的长度 */
   printf("Jacked a packet with length of [%d]
   ", header.len);
   /* 关闭会话 */
   pcap_close(handle);
   return(0);
   }
这个程序会嗅探由pcap_lookupdev()返回的设备并将它置为混杂模式,它发现第一个包经过端口23(telnet)并且告诉用户此包的大小(以字节为单位),之后调用pcap_close()关闭会话。实际应用中比较少用到pcap_next()。通常会使用pcap_loop()或者pcap_dispatch()(间接调用pcap_loop())。
pcap_loop函数
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
第一个参数是会话句柄,接下来是一个整型,它告诉pcap_loop()在返回前应捕捉多少个数据包(若为负值则表示一直工作直至错误发生)。第三个参数是回调函数的名称,最后一个参数在有些应用里有用,但更多的时候则置为NULL。假设有我们自己想送往回调函数的参数,另外还有pcap_loop()发送的参数,这就需要用到它。
pcap_dispatch()函数的用法与pcap_loop()函数唯一的不同是处理超时。后者忽略pcap_dispatch()设置的超时值。
回调函数的原型:
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
第一个参数对应着pcap_loop()的最后一个参数,每次回调函数被调用时,这个参数都会被传入;第二个参数是pcap头文件定义的,这个结构体包括数据包被嗅探的时间、大小等信息,其定义如下:
struct pcap_pkthdr
{
 struct timeval ts;  //时间戳
 bpf_u_int32 caplen;  //已捕捉部分的长度
 bpf_u_int32 len;  //该包的脱机长度
}
最后一个参数包含了被pcap_loop()嗅探到的所有包。这个数据包包含许多属性,它实质上是一个结构体的集合(比如,一个TCP/IP包会有一个以太网的头部,一个IP头部,一个TCP头部,还有此包的有效荷载),这个u_char就是这些结构体的串联版本。
下面是一些数据包的结构体:
       /* 以太网帧头部 */
   struct sniff_ethernet {
   u_char ether_dhost[ETHER_ADDR_LEN]; /* 目的主机的地址 */
   u_char ether_shost[ETHER_ADDR_LEN]; /* 源主机的地址 */
   u_short ether_type; /* IP? ARP? RARP? etc */
   };
   /* IP数据包的头部 */
   struct sniff_ip {
   #if BYTE_ORDER == LITTLE_ENDIAN
   u_int ip_hl:4, /* 头部长度 */
   ip_v:4; /* 版本号 */
   #if BYTE_ORDER == BIG_ENDIAN
   u_int ip_v:4, /* 版本号 */
   ip_hl:4; /* 头部长度 */
   #endif
   #endif /* not _IP_VHL */
   u_char ip_tos; /* 服务的类型 */
   u_short ip_len; /* 总长度 */
   u_short ip_id; /*包标志号 */
   u_short ip_off; /* 碎片偏移 */
   #define IP_RF 0x8000 /* 保留的碎片标志 */
   #define IP_DF 0x4000 /* dont fragment flag */
   #define IP_MF 0x2000 /* 多碎片标志*/
   #define IP_OFFMASK 0x1fff /*分段位 */
   u_char ip_ttl; /* 数据包的生存时间 */
   u_char ip_p; /* 所使用的协议 */
   u_short ip_sum; /* 校验和 */
   struct in_addr ip_src,ip_dst; /* 源地址、目的地址*/
   };
   /* TCP 数据包的头部 */
   struct sniff_tcp {
   u_short th_sport; /* 源端口 */
   u_short th_dport; /* 目的端口 */
   tcp_seq th_seq; /* 包序号 */
   tcp_seq th_ack; /* 确认序号 */
   #if BYTE_ORDER == LITTLE_ENDIAN
   u_int th_x2:4, /* 还没有用到 */
   th_off:4; /* 数据偏移 */
   #endif
   #if BYTE_ORDER == BIG_ENDIAN
   u_int th_off:4, /* 数据偏移*/
   th_x2:4; /*还没有用到 */
   #endif
   u_char th_flags;
   #define TH_FIN 0x01
   #define TH_SYN 0x02
   #define TH_RST 0x04
   #define TH_PUSH 0x08
   #define TH_ACK 0x10
   #define TH_URG 0x20
   #define TH_ECE 0x40
   #define TH_CWR 0x80
   #define TH_FLAGS (TH_FINTH_SYNTH_RSTTH_ACKTH_URGTH_ECETH_CWR)
   u_short th_win; /* TCP滑动窗口 */
   u_short th_sum; /* 头部校验和 */
   u_short th_urp; /* 紧急服务位 */
   };
pcap嗅探数据包时正是使用这些结构。接下来,它简单的创建一个u_char字符串并且将这些结构填入。
假定要对以太网上的TCP/IP包进行处理,同样的手段可以用于任何数据包,唯一的区别就是实际使用的结构体的类型,下面从声明分解u_char包的变量开始:
       const struct sniff_ethernet *ethernet; /* 以太网帧头部*/
   const struct sniff_ip *ip; /* IP包头部 */
   const struct sniff_tcp *tcp; /* TCP包头部 */
   const char *payload; /* 数据包的有效载荷*/
   /*为了让它的可读性好,我们计算每个结构体中的变量大小*/
   int size_ethernet = sizeof(struct sniff_ethernet);
   int size_ip = sizeof(struct sniff_ip);
   int size_tcp = sizeof(struct sniff_tcp);
   现在我们开始让人感到有些神秘的匹配:
   ethernet = (struct sniff_ethernet*)(packet);
   ip = (struct sniff_ip*)(packet + size_ethernet);
   tcp = (struct sniff_tcp*)(packet + size_ethernet + size_ip);
   payload = (u_char *)(packet + size_ethernet + size_ip + size_tcp);

基本的,当pcap将这些结构体填入u_char的时候是将这些数据存入一个字符串中,那个字符串将被送入回调函数中。反向转换是这样的,不考虑这些结构体制中的值,它们的大小将是一致的。例如在我的平台上,一个sniff_ethernet结构体的大小是14字节。一个sniff_ip结构体是20字节,一个sniff_tcp结构体也是20字节。 u_char指针正是包含了内存地址的一个变量,这也是指针的实质,它指向内存的一个区域。简单而言,指针指向的地址为x,假如三个结构体恰好线性排列,第一个(sniff_ethernet)被装载到内存地址的x处则可以很轻易的发现其他结构体的地址,表格显示如下:
   Variable Location (in bytes)
   sniff_ethernet X
   sniff_ip X + 14
   sniff_tcp X + 14 + 20
   payload X + 14 + 20 + 20
结构体sniff_ethernet正好在x处,紧接着它的sniff_ip则位于x加上它本身占用的空间(此例为14字节),依此类推可得全部地址。
注意:不要假设变量的大小。而应该使用sizeof()来确保尺寸的正确。这是因为这些结构体中的每个成员在不同平台下可以有不同的尺寸。

0 0
原创粉丝点击