网络抓包工具

简介:

http://blog.csdn.net/wangxg_7520/article/details/2795229

看了太多的“自己动手”,这次咱也“自己动手”一下,写个简单的网络抓包工具吧。要写出像tcpdump和wireshark(ethereal)这样的大牛程序来,咱也没那能耐,呵呵。所以这个工具只能抓取本地IP数据报,同时它还使用了BPF,目的是了解如何进行简单有效的网络抓包。

当打开一个标准SOCKET套接口时,我们比较熟悉的协议往往是用AF_INET来建立基于TCP(SOCK_STREAM)或UDP(SOCK_DGRAM)的链接。但是这些只用于IP层以上,要想从更底层抓包,我们需要使用AF_PACKET来建立套接字,它支持SOCK_RAW和SOCK_DGRAM,它们都能从底层抓包,不同的是后者得到的数据不包括以太网帧头(最开始的14个字节)。好了,现在我们就知道该怎样建立SOCKET套接口了:

  1. sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP));

最后一个参数 ETH_P_IP 指出,我们只对IP包感兴趣,而不是ARP,RARP等。之后就可以用recvfrom从套接口读取数据了。

现在我们可以抓到发往本地的所有IP数据报了,那么有没有办法抓到那些“流经”本地的数据呢?呵呵,当然可以了,这种技术叫网络嗅探(sniff),它很能威胁网络安全,也非常有用,尤其是当你对网内其他用户的隐私感兴趣时:(  由于以太网数据包是对整个网段广播的,所以网内所有用户都能收到其他用户发出的数据,只是默认的,网卡只接收目的地址是自己或广播地址的数据,而把不是发往自己的数据包丢弃。但是多数网卡驱动会提供一种混杂模式(promiscous mode),工作在这种模式下的网卡会接收网络内的所有数据,不管它是发给谁的。下面的方法可以把网卡设成混杂模式:

// set NIC to promiscous mode, so we can recieve all packets of the network
strncpy (ethreq.ifr_name, "eth0" , IFNAMSIZ);
ioctl(sock, SIOCGIFFLAGS, &ethreq);
ethreq.ifr_flags |= IFF_PROMISC;
ioctl(sock, SIOCSIFFLAGS, &ethreq);

  通过ifconfig可以很容易的查看当前网卡是否工作在混杂模式(PROMISC)。但是请注意,程序退出后,网卡的工作模式不会改变,所以别忘了关闭网卡的混杂模式:

// turn off promiscous mode
ethreq.ifr_flags &= ~IFF_PROMISC;
ioctl(sock, SIOCSIFFLAGS, &ethreq);

  现在我们可以抓到本网段的所有IP数据包了,但是问题也来了:那么多的数据,怎么处理?CPU可能会被严重占用,而且绝大多数的数据我们可能根本就不敢兴趣!那怎么办呢?用if语句?可能要n多个,而且丝毫不会降低内核的繁忙程度。最好的办法就是告诉内核,把不感兴趣的数据过滤掉,不要往应用层送。BPF就为此而生。

BPF(Berkeley Packet Filter)是一种类是汇编的伪代码语言,它也有命令代码和操作数。例如,如果我们只对用户192.168.1.4的数据感兴趣,可以用tcpdump的-d选项生成BPF代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$tcpdump -d host 192.168.1.4
(000) ldh      [12]
(001) jeq      #0x800           jt 2 jf 6
(002) ld       [26]
(003) jeq      #0xc0a80104      jt 12 jf 4
(004) ld       [30]
(005) jeq      #0xc0a80104      jt 12 jf 13
(006) jeq      #0x806           jt 8 jf 7
(007) jeq      #0x8035          jt 8 jf 13
(008) ld       [28]
(009) jeq      #0xc0a80104      jt 12 jf 10
(010) ld       [38]
(011) jeq      #0xc0a80104      jt 12 jf 13
(012) ret      #96
(013) ret      #0

  其中第一列代表行号,第二列是命令代码,后面是操作数。下面我们采用汇编注释的方式简单的解释一下:

 (000) ldh      [12] ;load h?? (2 bytes) from ABS offset 12 (the TYPE of ethernet header)
      (001) jeq      #0x800           jt 2 jf 6 ;compare and jump, jump to line 2 if true; else jump to line 6
      (002) ld       [26] ;load word (4 bytes) from ABS offset 26 (src IP address of IP header)
      (003) jeq      #0xc0a80104      jt 12 jf 4 ;compare and jump, jump to line 12 if true, else jump to line 4
      (004) ld       [30] ; load word (4 bytes) from ABS offset 30 (dst IP address of IP header)
      (005) jeq      #0xc0a80104      jt 12 jf 13 ;see line 3
      (006) jeq      #0x806           jt 8 jf 7 ;compare with ARP, see line 1
      (007) jeq      #0x8035          jt 8 jf 13 ;compare with RARP, see line 1
      (008) ld       [28] ;src IP address for other protocols
      (009) jeq      #0xc0a80104      jt 12 jf 10 
      (010) ld       [38] ;dst IP address for other protocols
      (011) jeq      #0xc0a80104      jt 12 jf 13 
      (012) ret      #96 ;return 96 bytes to user application
      (013) ret      #0 ;drop the packet

但是这样的伪代码我们是无法在应用程序里使用的,所以tcpdum提供了一个-dd选项来输出一段等效的C代码:

  1.    $tcpdump -dd host 192.168.1.4
  2.       { 0x28, 0, 0, 0x0000000c },
  3.       { 0x15, 0, 4, 0x00000800 },
  4.       { 0x20, 0, 0, 0x0000001a },
  5.       { 0x15, 8, 0, 0xc0a80104 },
  6.       { 0x20, 0, 0, 0x0000001e },
  7.       { 0x15, 6, 7, 0xc0a80104 },
  8.       { 0x15, 1, 0, 0x00000806 },
  9.       { 0x15, 0, 5, 0x00008035 },
  10.       { 0x20, 0, 0, 0x0000001c },
  11.       { 0x15, 2, 0, 0xc0a80104 },
  12.       { 0x20, 0, 0, 0x00000026 },
  13.       { 0x15, 0, 1, 0xc0a80104 },
  14.       { 0x6, 0, 0, 0x00000060 },
  15.       { 0x6, 0, 0, 0x00000000 },

该代码对应的数据结构是struct sock_filter,该结构在linux/filter.h中定义如下:

1
2
3
4
5
6
7
struct  sock_filter   // Filter block
  {
         __u16 code;  // Actual filter code
         __u8 jt;     // Jump true
         __u8 jf;     // Jump false
         __u32 k;    // Generic multiuse field
  };

  code对应命令代码;jt是jump if true后面的操作数,注意这里用的是相对行偏移,如2就表示向前跳转2行,而不像伪代码中使用绝对行号;jf为jump if false后面的操作数;k对应伪代码中第3列的操作数。

了解了BPF伪代码和结构,我们就可以自己定制更加简单有效的BPF filter了,如上例中的6-11行不是针对IP协议的,而我们的套接字已经指定只读取IP数据了,所以就可以把他们删除,不过要注意,行偏移也要做相应的修改。

另外,tcpdump默认只返回96字节的数据,但对大部分应用来说,96字节是远远不够的,所以tcpdump提供了-s选项用于指定返回的数据长度。

OK,下面我们就来看看怎样把过滤器安装到套接口上吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$tcpdump ip -d -s 2048 host 192.168.1.2
(000) ldh      [12]
(001) jeq      #0x800           jt 2 jf 7
(002) ld       [26]
(003) jeq      #0xc0a80102      jt 6 jf 4
(004) ld       [30]
(005) jeq      #0xc0a80102      jt 6 jf 7
(006) ret      #2048
(007) ret      #0
 
struct  sock_filter bpf_code[] = {
       { 0x28, 0, 0, 0x0000000c },
       { 0x15, 0, 5, 0x00000800 },
       { 0x20, 0, 0, 0x0000001a },
       { 0x15, 2, 0, 0xc0a80102 },
       { 0x20, 0, 0, 0x0000001e },
       { 0x15, 0, 1, 0xc0a80102 },
       { 0x6, 0, 0, 0x00000800 },
       { 0x6, 0, 0, 0x00000000 }
};
 
struct  sock_fprog filter;
filter.len =  sizeof (bpf_code)/ sizeof (bpf_code[0]);
filter.filter = bpf_code;
setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter,  sizeof (filter));

  最后加上信号处理器,以便能在程序退出前恢复网卡的工作模式。到现在我们已经可以看到一个小聚规模抓包小工具了,呵呵,麻雀虽小,但也五脏俱全啊!下面给出完整的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/types.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <net/if.h>
#include <stdio.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/filter.h>
#include <stdlib.h>
 
#define ETH_HDR_LEN 14
#define IP_HDR_LEN 20
#define UDP_HDR_LEN 8
#define TCP_HDR_LEN 20
 
static  int  sock;
 
void  sig_handler( int  sig)
{
       struct  ifreq ethreq;
       if (sig == SIGTERM)
             printf ( "SIGTERM recieved, exiting.../n" );
       else  if (sig == SIGINT)
             printf ( "SIGINT recieved, exiting.../n" );
       else  if (sig == SIGQUIT)
             printf ( "SIGQUIT recieved, exiting.../n" );
       // turn off the PROMISCOUS mode
       strncpy (ethreq.ifr_name,  "eth0" , IFNAMSIZ);
       if (ioctl(sock, SIOCGIFFLAGS, &ethreq) != -1) {
             ethreq.ifr_flags &= ~IFF_PROMISC;
             ioctl(sock, SIOCSIFFLAGS, &ethreq);
       }
       close(sock);
       exit (0);
}
 
int  main( int  argc,  char  ** argv) {
       int  n;
       char  buf[2048];
       unsigned  char  *ethhead;
       unsigned  char  *iphead;
       struct  ifreq ethreq;
       struct  sigaction sighandle;
 
#if 0
       $tcpdump ip -s 2048 -d host 192.168.1.2
       (000) ldh      [12]
       (001) jeq      #0x800           jt 2 jf 7
       (002) ld       [26]
       (003) jeq      #0xc0a80102      jt 6 jf 4
       (004) ld       [30]
       (005) jeq      #0xc0a80102      jt 6 jf 7
       (006) ret      #2048
       (007) ret      #0
#endif
 
       struct  sock_filter bpf_code[] = {
             { 0x28, 0, 0, 0x0000000c },
             { 0x15, 0, 5, 0x00000800 },
             { 0x20, 0, 0, 0x0000001a },
             { 0x15, 2, 0, 0xc0a80102 },
             { 0x20, 0, 0, 0x0000001e },
             { 0x15, 0, 1, 0xc0a80102 },
             { 0x6, 0, 0, 0x00000800 },
             { 0x6, 0, 0, 0x00000000 }
       };
 
       struct  sock_fprog filter;
       filter.len =  sizeof (bpf_code)/ sizeof (bpf_code[0]);
       filter.filter = bpf_code;
 
       sighandle.sa_flags = 0;
       sighandle.sa_handler = sig_handler;
       sigemptyset(&sighandle.sa_mask);
       //sigaddset(&sighandle.sa_mask, SIGTERM);
       //sigaddset(&sighandle.sa_mask, SIGINT);
       //sigaddset(&sighandle.sa_mask, SIGQUIT);
       sigaction(SIGTERM, &sighandle, NULL);
       sigaction(SIGINT, &sighandle, NULL);
       sigaction(SIGQUIT, &sighandle, NULL);
 
       // AF_PACKET allows application to read pecket from and write packet to network device
       // SOCK_DGRAM the packet exclude ethernet header
       // SOCK_RAW raw data from the device including ethernet header
       // ETH_P_IP all IP packets
       if ((sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP))) == -1) {
             perror ( "socket" );
             exit (1);
       }
 
       // set NIC to promiscous mode, so we can recieve all packets of the network
       strncpy (ethreq.ifr_name,  "eth0" , IFNAMSIZ);
       if (ioctl(sock, SIOCGIFFLAGS, &ethreq) == -1) {
             perror ( "ioctl" );
             close(sock);
             exit (1);
       }
 
       ethreq.ifr_flags |= IFF_PROMISC;
       if (ioctl(sock, SIOCSIFFLAGS, &ethreq) == -1) {
             perror ( "ioctl" );
             close(sock);
             exit (1);
       }
 
       // attach the bpf filter
       if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter,  sizeof (filter)) == -1) {
             perror ( "setsockopt" );
             close(sock);
             exit (1);
       }
 
       while (1) {
             n = recvfrom(sock, buf,  sizeof (buf), 0, NULL, NULL);
             if (n < (ETH_HDR_LEN+IP_HDR_LEN+UDP_HDR_LEN)) {
                   printf ( "invalid packet/n" );
                   continue ;
             }
 
             printf ( "%d bytes recieved/n" , n);
 
             ethhead = buf;
             printf ( "Ethernet: MAC[%02X:%02X:%02X:%02X:%02X:%02X]" , ethhead[0], ethhead[1], ethhead[2],
                   ethhead[3], ethhead[4], ethhead[5]);
             printf ( "->[%02X:%02X:%02X:%02X:%02X:%02X]" , ethhead[6], ethhead[7], ethhead[8],
                   ethhead[9], ethhead[10], ethhead[11]);
             printf ( " type[%04x]/n" , (ntohs(ethhead[12]|ethhead[13]<<8)));
 
             iphead = ethhead + ETH_HDR_LEN;
             // header length as 32-bit
             printf ( "IP: Version: %d HeaderLen: %d[%d]" , (*iphead>>4), (*iphead & 0x0f), (*iphead & 0x0f)*4);
             printf ( " TotalLen %d" , (iphead[2]<<8|iphead[3]));
             printf ( " IP [%d.%d.%d.%d]" , iphead[12], iphead[13], iphead[14], iphead[15]);
             printf ( "->[%d.%d.%d.%d]" , iphead[16], iphead[17], iphead[18], iphead[19]);
             printf ( " %d" , iphead[9]);
 
             if (iphead[9] == IPPROTO_TCP)
                   printf ( "[TCP]" );
             else  if (iphead[9] == IPPROTO_UDP)
                   printf ( "[UDP]" );
             else  if (iphead[9] == IPPROTO_ICMP)
                   printf ( "[ICMP]" );
             else  if (iphead[9] == IPPROTO_IGMP)
                   printf ( "[IGMP]" );
             else  if (iphead[9] == IPPROTO_IGMP)
                   printf ( "[IGMP]" );
             else
                   printf ( "[OTHERS]" );
 
             printf ( " PORT [%d]->[%d]/n" , (iphead[20]<<8|iphead[21]), (iphead[22]<<8|iphead[23]));
       }
       close(sock);
       exit (0);
}

  参考资料:
[1] Linux下Sniffer程序的实现
[2] 使用socket BPF

 

 


==============================================================================
本文转自被遗忘的博客园博客,原文链接:http://www.cnblogs.com/rollenholt/articles/2585364.html,如需转载请自行联系原作者

相关文章
|
2月前
|
安全 网络协议 算法
Nmap网络扫描工具详细使用教程
Nmap 是一款强大的网络发现与安全审计工具,具备主机发现、端口扫描、服务识别、操作系统检测及脚本扩展等功能。它支持多种扫描技术,如 SYN 扫描、ARP 扫描和全端口扫描,并可通过内置脚本(NSE)进行漏洞检测与服务深度枚举。Nmap 还提供防火墙规避与流量伪装能力,适用于网络管理、渗透测试和安全研究。
430 1
|
3月前
|
Web App开发 API 虚拟化
Cisco Modeling Labs (CML) 2.9.0 - 网络仿真工具
Cisco Modeling Labs (CML) 2.9.0 - 网络仿真工具
288 15
Cisco Modeling Labs (CML) 2.9.0 - 网络仿真工具
|
4月前
|
监控 安全 网络安全
网络安全工具及其使用方法:保护数字安全的第一道防线
在信息时代,网络攻击变得日益复杂且频繁,保护个人和企业数据安全的重要性日益凸显。幸运的是,各种网络安全工具为用户提供了有效的防护手段。从防火墙到密码管理器,这些工具覆盖了威胁检测、攻击防御和数据保护的方方面面。本文将介绍几款常用的网络安全工具,并提供其使用方法,以帮助您构建强大的网络安全防线。
187 1
|
4月前
|
监控 数据可视化 Java
VMware Aria Operations for Networks 6.14 - 网络和应用监控工具
VMware Aria Operations for Networks 6.14 - 网络和应用监控工具
108 0
VMware Aria Operations for Networks 6.14 - 网络和应用监控工具
|
5月前
|
运维 监控 Linux
网络延迟监测工具选择(第一篇)
**WGCLOUD**是一款开源免费的跨平台运维监控工具,支持Windows、Linux、MacOS等系统,具备网络延迟监测功能。其内置的**PING监测**模块可实时ping目标IP,图形化展示延迟趋势,并在目标IP不可达时发送告警通知。支持分组管理,操作简单便捷,适合运维人员高效监控网络状态。
|
10月前
|
数据采集 人工智能 自然语言处理
FireCrawl:开源 AI 网络爬虫工具,自动爬取网站及子页面内容,预处理为结构化数据
FireCrawl 是一款开源的 AI 网络爬虫工具,专为处理动态网页内容、自动爬取网站及子页面而设计,支持多种数据提取和输出格式。
3572 71
FireCrawl:开源 AI 网络爬虫工具,自动爬取网站及子页面内容,预处理为结构化数据
|
7月前
|
存储 监控 算法
基于 Python 哈希表算法的局域网网络监控工具:实现高效数据管理的核心技术
在当下数字化办公的环境中,局域网网络监控工具已成为保障企业网络安全、确保其高效运行的核心手段。此类工具通过对网络数据的收集、分析与管理,赋予企业实时洞察网络活动的能力。而在其运行机制背后,数据结构与算法发挥着关键作用。本文聚焦于 PHP 语言中的哈希表算法,深入探究其在局域网网络监控工具中的应用方式及所具备的优势。
220 7
|
10月前
|
Linux 网络性能优化 网络安全
Linux(openwrt)下iptables+tc工具实现网络流量限速控制(QoS)
通过以上步骤,您可以在Linux(OpenWrt)系统中使用iptables和tc工具实现网络流量限速控制(QoS)。这种方法灵活且功能强大,可以帮助管理员有效管理网络带宽,确保关键业务的网络性能。希望本文能够为您提供有价值的参考。
1718 28
|
11月前
|
存储 安全 物联网
浅析Kismet:无线网络监测与分析工具
Kismet是一款开源的无线网络监测和入侵检测系统(IDS),支持Wi-Fi、Bluetooth、ZigBee等协议,具备被动监听、实时数据分析、地理定位等功能。广泛应用于安全审计、网络优化和频谱管理。本文介绍其安装配置、基本操作及高级应用技巧,帮助用户掌握这一强大的无线网络安全工具。
875 9
浅析Kismet:无线网络监测与分析工具
|
10月前
|
网络协议 Unix Linux
深入解析:Linux网络配置工具ifconfig与ip命令的全面对比
虽然 `ifconfig`作为一个经典的网络配置工具,简单易用,但其功能已经不能满足现代网络配置的需求。相比之下,`ip`命令不仅功能全面,而且提供了一致且简洁的语法,适用于各种网络配置场景。因此,在实际使用中,推荐逐步过渡到 `ip`命令,以更好地适应现代网络管理需求。
437 11

热门文章

最新文章