网络抓包工具

简介:

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,如需转载请自行联系原作者

相关文章
|
8天前
|
安全 Linux 网络安全
nmap 是一款强大的开源网络扫描工具,能检测目标的开放端口、服务类型和操作系统等信息
nmap 是一款强大的开源网络扫描工具,能检测目标的开放端口、服务类型和操作系统等信息。本文分三部分介绍 nmap:基本原理、使用方法及技巧、实际应用及案例分析。通过学习 nmap,您可以更好地了解网络拓扑和安全状况,提升网络安全管理和渗透测试能力。
43 5
|
27天前
|
网络协议 安全 Linux
网络工具ping的使用方式
【10月更文挑战第19天】网络工具ping的使用方式
46 6
|
3月前
|
Web App开发 SQL 存储
警惕可能对Windows网络带来风险的工具
警惕可能对Windows网络带来风险的工具
警惕可能对Windows网络带来风险的工具
|
3月前
|
数据可视化 算法 C++
脑研究、脑网络分析、可视化的工具箱有哪些?
本文列举并简要介绍了用于脑研究、脑网络分析和可视化的多种工具箱,如Brain Connectivity Toolbox、bctpy、人类连接组项目等,为神经科学研究者提供了丰富的分析和可视化大脑网络的工具选择。
196 2
脑研究、脑网络分析、可视化的工具箱有哪些?
|
3月前
|
域名解析 运维 监控
网络故障排查的常用工具与方法:技术深度解析
【8月更文挑战第20天】网络故障排查是一项复杂而重要的工作,需要网络管理员具备扎实的网络知识、丰富的实践经验和灵活的问题解决能力。通过掌握常用工具和方法,遵循科学的排查流程,可以显著提高故障排查的效率和准确性。希望本文能为读者在网络故障排查方面提供有益的参考和启示。
|
3月前
|
传感器 监控 安全
网络监控工具的比较与选择:技术视角的深度剖析
【8月更文挑战第19天】网络监控工具的选择需要根据企业的实际需求、功能性能、成本和可维护性等多方面因素进行综合考虑。通过对SolarWinds、Zabbix、PRTG和Nagios等主流网络监控工具的比较,我们可以看到每种工具都有其独特的优势和适用场景。因此,在选择时,请务必根据您的具体情况进行权衡和选择,以确保您能够获得最佳的监控效果和投资回报。
|
3月前
|
监控 安全 网络协议
这10款网络扫描工具,是个网工,都想全部安装!
这10款网络扫描工具,是个网工,都想全部安装!
256 1
|
3月前
|
人工智能 安全 数据可视化
2024黑帽大会最热门的九大AI网络安全工具
在2024年的黑帽大会(Black Hat 2024)上,AI驱动的网络安全工具和技术成为焦点,引领了网络安全行业新趋势。众多安全厂商和初创公司展示了他们的最新成果,利用生成式AI来管理风险、检测并对抗网络犯罪,保障企业安全。以下是大会上一些备受瞩目的AI驱动网络安全产品和服务:
|
3月前
|
存储 监控 安全
常用的网络安全工具有哪些?
【8月更文挑战第7天】
190 4
|
4月前
|
存储 Prometheus 监控

热门文章

最新文章

下一篇
无影云桌面