交换型网络环境嗅探原理及LINUX下的实现

传统的嗅探器在共享型以太网中可以捕获到所有数据包,但在交换型以太网中却无法工作。本文介绍利用 ARP 欺骗模式,改变其它主机 arp 高速缓存中的记录,使所有数据包都到达本机且由本机转发,从而达到捕获数据包的效果。

施聪 (javer@163.com), 程序员

施聪,成都人,程序员。长期从事基于UNIX/LINUX下的c/c++程序设计。近期学习和研究的方向为J2EE,设计模式,软件工程。可通过 javer@163.comshicong1973@163.com和他联系。



2002 年 12 月 09 日

1. 以太网嗅探原理

以太网环境下的嗅探本身是比较简单的,只要网卡能设置成混杂模式且有数据包到达网卡,则可用多种方法捕获数据包并进行各种协议分析。在LINUX下可用RAW套接字,SOCK_PACKET套接字, LIBPCAP函数包等方法捕获数据包,典型的应用程序如TCPDUMP, LINUX_SNIFFER等。

在共享型以太网中,上述两个条件显然是满足的。所有的主机都连接到HUB,而HUB对数据包传输形式是广播。这意味着发给某个主机的数据包也会被其它所有主机的网卡所收到。因此在这样的环境中,任何设置成混杂模式的主机,都可以捕获发送给其它主机的数据包,从而窃听网络上的所有通信。

在交换型以太网中,上述条件2是不满足的。所有的主机连接到SWITCH,SWITCH比HUB更聪明,它知道每台计算机的MAC地址信息和与之相连的特定端口,发给某个主机的数据包会被SWITCH从特定的端口送出,而不是象HUB那样,广播给网络上所有的机器。这种传输形式使交换型以太网的性能大大提高,同时还有一个附加的作用:使传统的嗅探器无法工作。

综上所述,交换型网络环境嗅探的核心问题是:如何使本不应到达的数据包到达本地。通常的方法有MAC洪水包和ARP欺骗。其中MAC洪水包是向交换机发送大量含有虚构MAC地址和IP地址的IP包,使交换机无法处理如此多的信息,致使交换机就进入了所谓的"打开失效"模式,也就是开始了类似于集线器的工作方式,向网络上所有的机器广播数据包。(具体实现请参阅Dsniff中的macof)本文中,我们将要详细分析ARP欺骗模式。


2. 相关协议数据包格式

  • 以太数据包格式:
    目的MAC地址源MAC地址类型数据
    66246~1500

    类型0800 :IP数据包
    类型0806 :ARP数据包
  • ARP数据包格式:
    目的端 MAC 地址源 M A C 地 址0 8 0 6硬 件 类 型协 议 类 型硬 件 地 址 长 度协 议 地 址 长 度A R P 包 类 型发 送 端 M A C 地 址发 送 端 I P 地 址目 的 端 M A C 地 址目 的 端 I P 地 址
    662221126464
    <------以太网首部--------><------26字节ARP请求/应答-------->
  • ARP数据包简化格式
    为了论述的简洁性,我们把ARP数据包格式做一些简化。
    目的端MAC地址源MAC地址ARP包类型发送端MAC地址发送端IP地址目的端MAC地址目的端IP地址

3. 实验环境

为了更清楚的描述交换网络的嗅探原理,我们建立一个虚构的交换网络环境, 在下面的论述中将用到这些数据.

主机编号IP地址MAC地址备注
A1.1.1.101:01:01 
B2.2.2.202:02:02 
C3.3.3.303:03:03 
D4.4.4.404:04:04(我)运行嗅探器

4. ARP协议原理

在以太网中传输的数据包是以太包,而以太包的寻址是依据其首部的物理地址(MAC地址)。仅仅知道某主机的逻辑地址(IP地址)并不能让内核发送一帧数据给此主机,内核必须知道目的主机的物理地址才能发送数据。ARP协议的作用就是在于把逻辑地址变换成物理地址,也既是把32bit的IP地址变换成48bit的以太地址。

每一个主机都有一个ARP高速缓存,此缓存中记录了最近一段时间内其它IP地址与其MAC地址的对应关系。如果本机想与某台主机通信,则首先在ARP高速缓存中查找此台主机的IP和MAC信息,如果存在,则直接利用此MAC地址构造以太包;如果不存在,则向本网络上每一个主机广播一个ARP请求包,其意义是"如果你有此IP地址,请告诉我你的MAC地址",目的主机收到此请求包后,发送一个ARP响应包,本机收到此响应包后,把相关信息记录在ARP高速缓存中,以下的步骤同上。

可以看出,ARP协议是有缺点的,第三方主机可以构造一个ARP欺骗包,而源主机却无法分辨真假。


5. ARP欺骗原理

假设B(2.2.2.2)要与A(1.1.1.1)通信,且B的ARP高速缓存中没有关于A的MAC信息,则B发出ARP请求包。

FF:FF:FF02:02:02请求02:02:022.2.2.2FF:FF:FF1.1.1.1
广播B的MAC B的MACB的IP目的地址A的IP

正常情况下A向B发出ARP应答包:

02:02:0201:01:01应答01:01:011.1.1.102:02:022.2.2.2
B的MACA的MAC A的MACA的IPB的MACB的IP

我捕获到B的ARP请求包后,构造ARP欺骗包(欺骗B对A的连接)

02:02:0204:04:04应答04:04:041.1.1.102:02:022.2.2.2
B的MAC我的MAC 我的MACA的IPB的MACB的IP

此时,B的ARP高速缓存中关于A的记录为(1.1.1.1 <-- --> 04:04:04),则B向A发IP包实际上是发到我的主机(4.4.4.4,04:04:04)。同理,如果我进一步欺骗A,让A的ARP高速缓存中关于B的记录为(2.2.2.2 <-- --> 04:04:04), 则A向B发IP包实际上也是发到我的主机(4.4.4.4,04:04:04)。最后,我让本机打开数据包转发,也既是充当路由器,则A,B之间能正常通信,但我能全部捕获到相关数据。

以上讨论的是欺骗两台主机,如果我能让局域网中每一台主机的ARP高速缓存中关于其它任意一个主机所对应的MAC地址都为我的MAC地址(04:04:04:04),则本局域网中所有数据包我都能捕获到!


6. 程序设计思路

  • 使用到的函数包
    libpcap : 捕获ARP数据包。
    libnet : 获得本机的MAC地址和IP地址;构造和发送ARP欺骗包。
    这两个的函数包的使用在网上资料很多,本文中不介绍。
  • 主要数据结构
    程序中有两个全局变量,MYIP代表本机的IP地址,MYMAC代表本机的MAC地址。
    程序中维护一个存放主机信息的链表:
    typedef struct host HOST;
    struct host
    {
    		unsigned long  ip;              // IP地址
    		unsigned char  mac[6];          // MAC地址
    int mac_flag;   					//  0:MAC为空,1:MAC不为空
    		HOST  * next;
    };
  • 掌握本局域网中每一台主机的IP地址和MAC地址信息。
    利用libpcap捕获网络中的ARP请求/应答包,最大限度的提取相关信息。如在第五节中的B对A的ARP请求包,我们可以提取出关于B的完整信息(2.2.2.2,02:02:02),也获得了关于A的部分信息(1.1.1.1,null)。在知道网络中有主机A的情况下,我们可以构造并发送对A的ARP请求包,捕获A的ARP应答包,从而完整掌握A的信息。同理,我们也可以捕获TCP/UDP等数据包,从中提取信息。
    创建一个向链表增加主机信息的函数:add_host(ip,mac), 每收到一个ARP请求/应答包,都执行add_host( )两次:add_host(发送端IP,发送端MAC),add_host(目的端IP,目的端MAC)。
    在收到ARP应答包时,首先检查发送端的IP和MAC,如果IP不是自己的,但MAC是自己的,则说明此应答包是本机构造的ARP欺骗包,程序忽略。
    对于正常的ARP请求包和应答包,add_host(ip, mac )中IP或MAC只要有一个是自己的(ip == MYIP || mac ==MYMAC),则程序忽略。显然,没有必要自己欺骗自己。
    add_host(ip,mac)遍历主机链表,如果IP存在,且MAC不空,则把MAC地址写入;如果不存在,则增加一个HOST节点,写入IP地址,如果MAC不空,则也把MAC地址写入。 注意到这样一个情况:在ARP请求包中,目的MAC地址是没有意义的,所以我们只写入IP地址,而MAC地址用NULL来表示。这是我们收集网络拓朴结构的一种被动方法。

    函数add_host( )逻辑设计MYIP = IP(d),MYMAC = MAC(d)

    图 1

    代码如下:

    void add_host(u_long ip, u_char * mac)
    {
    		HOST * new = NULL;
    		HOST * cur = NULL;
    
    		if( (ip == MYIP) || (mac && mac_equal(mac, MYMAC)) )
    			return;
    
    		//遍历链表查询IP地址
    		for(cur = head; cur; cur = cur->next)
    		{
    			if( ip == cur->ip )
    			{
    				if( mac )  // MAC地址不空,则写入
    				{
    					memcpy(cur->mac, mac, ETHER_ADDR_LEN);
    					cur->mac_flag = 1;
    				}	
    				return;
    			}	
    		}
    		if(cur == NULL)  // 链表中没有此IP地址
    		{
    			new = (HOST *)malloc(sizeof(HOST));
    			new->ip = ip;
    			if( mac )
    			{
    				memcpy(new->mac, mac, ETHER_ADDR_LEN);
    				new->mac_flag = 1;
    			}	
    			else
    				new->mac_flag = 0;
    			new->next = NULL;
    			if(! head)     // 把新节点加入链表
    			{
    				head = new;
    				tail = new;
    			}
    			else
     			{
    				tail->next = new;
    				tail = new;
    			}  
    		} 
    		return;
    }
  • 周期性的向局域网中每一台主机发送ARP欺骗包。
    创建一个发送ARP欺骗包的函数send_fake_arp_packet(),遍历主机链表的每一个IP地址,如果此IP地址的MAC地址已知,则遍历主机链表中其它IP地址,以其它IP地址和本机的MAC地址为发送端,以选中的IP地址和MAC地址为目的端,构造并发送ARP应答欺骗包;如果此IP地址的MAC地址未知,则以本机IP地址和MAC地址为发送端,以选中的IP地址为目的端,构造并发送正常的ARP请求包。注意,这是我们收集网络拓朴结构的一种主动方法。
    ARP高速缓存中的记录都有过期时间,不同的操作系统有不同的设置.在实施中,我们只需以较短的时间周期性的发ARP欺骗包,则可解决这个问题.
    代码如下:
      void send_fake_arp_packet()
    {
        HOST * temp, * cur;
        u_char broad[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
      
    for(cur = head; cur ; cur = cur->next)
        {
          
    if( cur->mac_flag == 0)       
    {
    // 构造ARP请求包请求此IP地址的MAC地址
            libnet_build_ethernet(broad, MYMAC, ETHERTYPE_ARP, NULL, 0, packet);
            libnet_build_arp(ARPHRD_ETHER, ETHERTYPE_IP, ETHER_ADDR_LEN, 4, ARPOP_REQUEST,
            MYMAC, (u_char *)&MYIP, (u_char *)broad,(u_char *)&cur->ip, 
                 NULL, 0, (packet + LIBNET_ETH_H));
            if((libnet_write_link_layer(netif, device, packet, (LIBNET_ETH_H + LIBNET_ARP_H))) < 0)
            errexit("libnet_write_link_layer error\n");
    
          continue;
        }  
        for(temp = head; temp; temp = temp->next)
        {
          if (temp == cur)
            continue;
    
          libnet_build_ethernet(cur->mac, MYMAC, ETHERTYPE_ARP, NULL, 0, packet);
          libnet_build_arp(ARPHRD_ETHER, ETHERTYPE_IP, ETHER_ADDR_LEN, 4, ARPOP_REPLY,
            MYMAC, (u_char *)&temp->ip, cur->mac,(u_char *)&cur->ip, NULL, 0, 
                 (packet + LIBNET_ETH_H));
          if((libnet_write_link_layer(netif, device, packet, (LIBNET_ETH_H + LIBNET_ARP_H))) < 0)
            errexit("libnet_write_link_layer error\n");
        }
      }  
    }
  • 正确处理一个以太包实际被捕获两次的情况。 例如:捕获到B发向A的以太包,第一次为(2.2.2.2,02:02:02)->(1.1.1.1,04:04:04),第二次为(2.2.2.2,04:04:04)->(1.1.1.1,01:01:01)。区分这两次捕获的规则很简单:目标IP 不为本IP且目标MAC为本MAC就是第一次;源IP不为本IP且源MAC为本MAC就是第二次。在实际处理中,可任取一种包并把源/目的MAC改写,对另一种包忽略即可。

7. 相关说明

  • 这种ARP欺骗是以主机的ARP高速缓存可以动态改变为前提的,如果ARP高速缓存中某一IP的MAC地址被设为静态(static),则对关于此IP的欺骗是显然不成立的。
  • 为了尽快的掌握网络中主机的地址信息,应至少对ARP请求包和ARP应答包都处理。如果只处理ARP请求包,则在此刻只能掌握发端主机的信息,并只能欺骗发端主机。在实际的IP包捕获中,就会看到单边数据包。当然,在程序运行一段时间后,所有主机信息也能全部掌握,但效率显然是不高的。如果再加上对未知MAC的IP的主动请求,则捕获所有数据包可在较短时间内完成。(在实际的C类网中测试,不到一分钟)

参考资料

本文的样码 在此下载。

dsniff: http://naughty.monkey.org/~dugsong/dsniff/

http://sunsite.uba.ar/download/pub/OpenBSD/snapshots/packages/i386/

http://www.cpress.cz/knihy/tcp-ip-bezp/Download/dsniff/

angst: http://sourceforge.net/project/showfiles.php?group_id=14044&release_id=23186

http://www.zone-h.org/en/download/category=52/

《交换网络嗅探》:作者:Sumit Dhar < sumit.dhar@slmsoft.com>

翻译:coolfrog < coolfrog@usa.com>

《TCP/IP详解 卷一》:作者 W.Richard Stevens

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Linux
ArticleID=188712
ArticleTitle=交换型网络环境嗅探原理及LINUX下的实现
publish-date=12092002