简介
嗅探 sniff。嗅探器可以窃听网路上流经的封包。用集线器hub组建的网路是基于共享的原理的,区域网路内所有的电脑都接收相同的封包,而网卡构造了硬体的过滤器通过识别MAC地址过滤掉和自己无关的信息,嗅探程式只需关闭这个过滤器,将网卡设定为混杂模式就可以进行嗅探。用交换机switch组建的网路是基于交换原理的,交换机不是把封包发到所有的连线埠上,而是发到目的网卡所在的连线埠,这样嗅探起来会麻烦一些,嗅探程式一般利用ARP欺骗的方法,通过改变MAC地址等手段,欺骗交换机将封包发给自己,嗅探分析完毕再转发出去。
相关介绍
SNIFF真是一个古老的话题,关于在网路上採用SNIFF来获取敏感信息已经不是什麽新鲜事,也不乏很多成功的案例,那麽,SNIFF究竟是什麽呢?SNIFF就是嗅探器,就是窃听器,SNIFF静悄悄的工作在网路的底层,把你的秘密全部记录下来。看过威尔史密斯演的《全民公敌》吗?SNIFF就象裏面精巧的窃听器一样,让你防不胜防。
SNIFF可以是软体,也可以是硬体,既然是软体那就要分平台,有WINDOWS下的、UNIX下的等,硬体的SNIFF称为网路分析仪,反正不管硬体软体,目标只有一个,就是获取在网路上载输的各种信息。本文仅仅介绍软体的SNIFF。
当你舒适的坐在家裏,惬意的享受网路给你带来的便利,收取你的EMAIL,购买你喜欢的物品的时候,你是否会想到你的朋友给你的信件,你的额度卡帐号变成了一个又一个的信息包在网路上不停的传送着,你是否想过,这些信息包会通过网路流入别人的机器呢?你的担忧不是没有道理的,因为SNIFF可以让你的担忧变成实实在在的危险。就好像一个人躲在你身后偷看你一样。
网路基础知识
网路基础知识,是不是听起来有点离题了?虽然听起来这和我们要谈的SNIFF没什麽关系,可是还是要说一说的,万丈高楼平地起,如果连地基都没打好,怎麽盖楼?!如果你对网路还不是十厘清楚的话,最好能静下心来好好看看,要知道,这是基础的基础,在这裏我只是简单的说一下,免得到时候有人迷糊,详细的最好能够自己去找书看看。
TCP/IP体系结构
开放系统互连(OSI)模型将网路划分为七层模型,分别用以在各层上实现不同的功能,这七层分别为:套用层、表示层、会话层、传输层、网路层、资料链路层及物理层。而TCP/IP体系也同样遵循这七层标準,只不过在某些OSI功能上进行了压缩,将表示层及会话层合并入套用层中,所以实际上我们打交道的TCP/IP仅仅有5层而已,网路上的分层结构决定了在各层上的协定分布及功能实现,从而决定了各层上网路设备的使用。实际上很多成功的系统都是基于OSI模型的,如:如帧中继、ATM、ISDN等。
TCP/IP的网路体系结构(部分)
从上面的图中我们可以看出,第一层物理层和第二层资料链路层是TCP/IP的基础,而TCP/IP本身并不十分关心低层,因为处在资料链路层的网路设备驱动程式将上层的协定和实际的物理接口隔离开来。网路设备驱动程式位于介质访问子层(MAC)
网路上的设备
中继器:中继器的主要功能是终结一个网段的信号并在另一个网段再生该信号,一句话,就是简单的放大而已,工作在物理层上。
网 桥:网桥使用MAC物理地址实现中继功能,可以用来分隔网段或连线部分异种网路,工作在资料链路层。
路由器:路由器使用网路层地址(IP,X.121,E.164等),主要负责封包的路由寻径,也能处理物理层和资料链路层上的工作。
网 关:主要工作在网路第四层以上,主要实现收敛功能及协定转换,不过很多时候网关都被用来描述任何网路互连设备。
TCP/IP与乙太网
乙太网和TCP/IP可以说是相辅相成的,可以说两者的关系几乎是密不可分,乙太网在一二层提供物理上的连线,而TCP/IP工作在上层,使用32位的IP地址,乙太网则使用48位的MAC地址,两者间使用ARP和RARP协定进行相互转换。从我们上面TCP/IP的模型图中可以清楚的看到两者的关系。
载波监听/沖突检测(CSMA/CD)技术被普遍的使用在乙太网中,所谓载波监听是指在乙太网中的每个站点都具有同等的权利,在传输自己的资料时,首先监听信道是否空闲,如果空闲,就传输自己的资料,如果信道被佔用,就等待信道空闲。而沖突检测则是为了防止发生两个站点同时监测到网路没有被使用时而产生沖突。乙太网採用广播机製,所有与网路连线的工作站都可以看到网路上载递的资料。
为了加深你的理解,我们来看看下面的图,一个典型的在乙太网中客户与伺服器使用TCP/IP协定的通信
乙太网
唆唆了这麽多,有人烦了吧?相信我,这是基础的基础,可以说是说得是很简单拉,如果需要,拿出个几十万字来说上面的内容,我想也不嫌多,好了,让我们进入下一节,sniff的原理。
SNIFF原理
要知道在乙太网中,所有的通讯都是广播的,也就是说通常在同一个网段的所有网路接口都可以访问在物理媒体上载输的所有资料,而每一个网路接口都有一个唯一的硬体地址,这个硬体地址也就是网卡的MAC地址,大多数系统使用48比特的地址,这个地址用来表示网路中的每一个设备,一般来说每一块网卡上的MAC地址都是不同的,每个网卡厂家得到一段地址,然后用这段地址分配给其生产的每个网卡一个地址。在硬体地址和IP地址间使用ARP和RARP协定进行相互转换。
在正常的情况下,一个网路接口应该只回响这样的两种资料帧:
1.与自己硬体地址相匹配的资料帧。
2.发向所有机器的广播资料帧。
在一个实际的系统中,资料的收发是由网卡来完成的,网卡接收到传输来的资料,网卡内的单片程式接收资料帧的目的MAC地址,根据电脑上的网卡驱动程式设定的接收模式判断该不该接收,认为该接收就接收后产生中断信号通知CPU,认为不该接收就丢掉不管,所以不该接收的资料网卡就截断了,电脑根本就不知道。CPU得到中断信号产生中断,作业系统就根据网卡的驱动程式设定的网卡中断程式地址调用驱动程式接收资料,驱动程式接收资料后放入信号堆叠让作业系统处理。而对于网卡来说一般有四种接收模式:
广播方式:该模式下的网卡能够接收网路中的广播信息。 组播方式:设定在该模式下的网卡能够接收组播资料。
直接方式:在这种模式下,只有目的网卡才能接收该资料。混杂模式:在这种模式下的网卡能够接收一切通过它的资料,而不管该资料是否是传给它的。
好了,现在我们总结一下,首先,我们知道了在乙太网中是基于广播方式传送资料的,也就是说,所有的物理信号都要经过我的机器,再次,网卡可以置于一种模式叫混杂模式(promiscuous),在这种模式下工作的网卡能够接收到一切通过它的资料,而不管实际上资料的目的地址是不是他。这实际上就是我们SNIFF工作的基本原理:让网卡接收一切他所能接收的资料。
我们来看一个简单的例子,如图一所示,机器A、B、C与集线器HUB相连线,集线器HUB通过路由器Router访问外部网路。这是一个很简单也很常见的情况,比如说在公司大楼裏,我所在的网路部办公室裏的几台机器通过集线器连线,而网路部、开发部、市场部也是同样如此,几个部门的集线器通过路由器连线。还是回到我们的图一上来,值得注意的一点是机器A、B、C使用一个普通的HUB连线的,不是用SWITCH,也不是用ROUTER,使用SWITCH和ROUTER的情况要比这复杂得多。
我们假设一下机器A上的管理员为了维护机器C,使用了一个FTP命令向机器C进行远程登入,那麽在这个用HUB连线的网路裏资料走向过程是这样的。首先机器A上的管理员输入的登入机器C的FTP口令经过套用层FTP协定、传输层TCP协定、网路层IP协定、资料链路层上的乙太网驱动程式一层一层的包裹,最后送到了物理层,我们的网线上。接下来资料帧送到了HUB上,现在由HUB向每一个接点广播由机器A发出的资料帧,机器B接收到由HUB广播发出的资料帧,并检查在资料帧中的地址是否和自己的地址相匹配,发现不是发向自己的后把这资料帧丢弃,不予理睬。而机器C也接收到了资料帧,并在比较之后发现是发现自己的,接下来他就对这资料帧进行分析处理。
在上面这个简单的例子中,机器B上的管理员如果很好奇,他很想知道究竟登入机器C上FTP口令是什麽?那麽他要做的很简单,仅仅需要把自己机器上的网卡置于混杂模式,并对接收到的资料帧进行分析,从而找到包含在资料帧中的口令信息。
sniff做法
在上一节裏,我们已经知道了SNIFF的基本原理是怎麽一回事,这一节我们来亲自动手做一个自己的sniff,毕竟,用程式代码来说话比什麽都要来得真实,也容易加深理解。
回头想一想我们上面说的原理,我们要做的事情有几件:
1. 把网卡置于混杂模式。 2. 捕获封包。 3.分析封包。
注:下面的原始码取至Chad Renfro的《Basic Packet-SnifferConstruction from the Ground Up》一文中
/************************Tcp_sniff_2.c********************/
1.#include
2.#include
3.#include
4.#include
5.#include
6.#include
7.#include
8.#include
9.#include headers.h
#define INTERFACE eth0
/*Prototype area*/
1 0 int Open_Raw_Socket(void);
11 int Set_Promisc(char *interface,intsock);
12 int main() {
13int sock,bytes_recieved,fromlen;
14.char buffer[65535];
15.struct sockaddr_in from;
16.struct ip *ip;
17.struct tcp *tcp;
18.sock = Open_Raw_Socket();
19. Set_Promisc(INTERFACE,sock);
20. while(1)
22. {
23. fromlen = sizeof from;
24. bytes_recieved = recvfrom(sock,buffer,sizeofbuffer,0,(struct sockaddr *)&from,&fromlen);
25. printf(\nBytes received :::%5d\n,bytes_recieved);
26. printf(Source address :::%s\n,inet_ntoa(from.sin_addr));
27. ip = (struct ip *)buffer;
/*See if this is a TCP packet*/
28. if(ip->ip_protocol == 6) {
29. printf(IP header length :::%d\n,ip->ip_length);
30. printf(Protocol :::%d\n,ip->ip_protocol);
31. tcp = (struct tcp *)(buffer +(4*ip->ip_length));
32. printf(Source port :::%d\n,ntohs(tcp->tcp_source_port));
33. printf(Dest port :::%d\n,ntohs(tcp->tcp_dest_port));
34. }
35. }
36.}
37 int Open_Raw_Socket() {
38. int sock;
39. if((sock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP)) < 0){
/*Then the socket was not created properly and must die*/
40. perror(The raw socket was not created);
41. exit(0);
42. };
43. return(sock);
44. }
45 int Set_Promisc(char *interface,int sock ) {
46. struct ifreq ifr;
47. strncpy(ifr.ifr_name,interface,strnlen(interface)+1);
48. if((ioctl(sock,SIOCGIFFLAGS,&ifr) == -1)) {
/*Could not retrieve flags for the interface*/
49. perror(Could not retrive flags for the interface);
50. exit(0);
51. }
52. printf(The interface is ::: %s\n,interface);
53. perror(Retrieved flags from interface successfully);
54. ifr.ifr_flags |= IFF_PROMISC;
55. if (ioctl (sock,SIOCSIFFLAGS,&ifr) == -1 ) {
/*Could not set the flags on the interface */
56. perror(Could not set the PROMISC flag:);
57. exit(0);
58. }
59. printf(Setting interface ::: %s ::: to promisc,interface);
60. return(0);
61. }
/***********************EOF**********************************/
上面这段程式中有很详细的注解,不过我想还是有必要说一说,首先第10行--intOpen_Raw_Socket(void); 是我们的自定义函式,具体内容如下:
37 int Open_Raw_Socket() {
38. int sock;
39. if((sock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP)) < 0){
/*Then the socket was not created properly and must die*/
40. perror(The raw socket was not created);
41. exit(0);
42. };
43. return(sock);
44. }
第39行 if((sock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP)) < 0) {
这裏我们调用了socket函式,使建立了了一个原始套接口,使之收到TCP/IP信息包。
接下来第11行-int Set_Promisc(char *interface,intsock),这也是我们的自定义函式,目的是把网卡置于混杂模式,具体内容如下:
45 int Set_Promisc(char *interface,int sock ) {
46. struct ifreq ifr;
47. strncpy(ifr.ifr_name,interface,strlen(interface)+1);
48. if((ioctl(sock,SIOCGIFFLAGS,&ifr) == -1)) {
/*Could not retrieve flags for the interface*/
49. perror(Could not retrive flags for the interface);
50. exit(0);
51. }
52. printf(The interface is ::: %s\n,interface);
53. perror(Retrieved flags from interface successfully);
54. ifr.ifr_flags |= IFF_PROMISC;
55. if (ioctl (sock,SIOCSIFFLAGS,&ifr) == -1 ) {
/*Could not set the flags on the interface */
56. perror(Could not set the PROMISC flag:);
57. exit(0);
58. }
59. printf(Setting interface ::: %s ::: to promisc,interface);
60. return(0);
61. }
首先 struct ifreq ifr; 定一了一个ifrreg的结构ifr,接下来strncpy(ifr.ifr_name,interface,strnlen(interface)+1);,就是把我们网路设备的名字填充到ifr结构中,在这裏#define INTERFACE eth0 ,让我们再往下看,ioctl(sock,SIOCGIFFLAGS,&ifr),SIOCGIFFLAGS请求表示需要获取接口标志,现在到了第54行,在我们成功的获取接口标志后把他设定成混杂模式,ifr.ifr_flags|= IFF_PROMISC;ioctl (sock,SIOCSIFFLAGS,&ifr)。OK,现在我们所说的第一步已经完成--------把网卡置于混杂模式。
现在进入第二步,捕获封包。从第20行开始,我们进入了一个死迴圈,while(1),在第24行,recvfrom(sock,buffer,sizeof buffer,0,(struct sockaddr *)&from,&fromlen),这个函式要做的就是接收资料,并把接收到的资料放入buffer中。就是这麽简单,已经完成了我们要捕获封包的任务。
到了第三步,分析封包。27行,ip = (struct ip*)buffer,使我们在头档案中的IP结构对应于所接收到的资料,接下来判断在网路层中是否使用的是TCP协定,if(ip->ip_protocol== 6) ,如果答案是,tcp信息包从整个IP/TCP包 buffer +(4*ip->ip_length) 地址处开始,所以31行 tcp = (struct tcp*)(buffer +(4*ip->ip_length)),然后对应结构把你所需要的信息输出。
/*************************headers.h**************************/
/*structure of an ip header*/
struct ip {
unsigned int ip_length:4; /*little-endian*/
unsigned int ip_version:4;
unsigned char ip_tos;
unsigned short ip_total_length;
unsigned short ip_id;
unsigned short ip_flags;
unsigned char ip_ttl;
unsigned char ip_protocol;
unsigned short ip_cksum;
unsigned int ip_source; unsigned int ip_dest;
};
/* Structure of a TCP header */
struct tcp {
unsigned short tcp_source_port;
unsigned short tcp_dest_port;
unsigned int tcp_seqno;
unsigned int tcp_ackno;
unsigned int tcp_res1:4,/*little-endian*/
tcp_hlen:4,
tcp_fin:1,
tcp_syn:1,
tcp_rst:1,
tcp_psh:1,
tcp_ack:1,
tcp_urg:1,
tcp_res2:2;
unsigned short tcp_winsize;
unsigned short tcp_cksum;
unsigned short tcp_urgent;
};
/*********************EOF***********************************/
从上面的分析我们可以清楚的认识到,认识一个SNIFF需要对TCP/IP协定有着详细的了解,否则你根本无法找到你需要的信息。有了上面的基础,你可以自己来做一个你需要的SNIFF了。
















