Chinaunix首页 | 论坛 | 博客
  • 博客访问: 386459
  • 博文数量: 124
  • 博客积分: 2911
  • 博客等级: 少校
  • 技术积分: 1050
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-15 15:57
文章分类

全部博文(124)

文章存档

2012年(6)

2011年(26)

2010年(92)

我的朋友

分类: 系统运维

2010-05-20 15:00:33

第一篇 手把手教你玩转ARP
目录:
.关于ARP协议的基础知识
1.ARP的工作原理
2.ARP包的格式
3.ARP包的填充
二。发送数据包的编程实现
1.填充数据包
2.发送数据包
三。一些附加步骤及说明
1.如果在VC中使用winpcap
2.获得网卡信息列表
3.获得系统ARP信息列表
四。ARP包的游戏
1.小伎俩
2.ARP欺骗的实现
3.基于ARP欺骗的监听原理 
 
一.             关于ARP协议的 基础知识
1.ARP的工作原理
本来我不想在此重复那些遍地都是的关于ARP的基本常识,但是为了保持文章的完整性以及照顾初学者,我就再啰嗦一些 文字吧,资深读者可以直接跳过此节。
 
我们都知道以太网设备比如网卡都有自己全球唯一的MAC地址,它们是以MAC地址来传输以太网数据包的,但是它们却 识别不了我们IP包中的IP地址,所以我们在以太网中进行IP通信的时候就需要一个协议来建立IP地址与MAC地址的对应关系,以使IP数据包能发到一个 确定的地方去。这就是ARP(Address Resolution Protocol,地址解析协议)。
 
讲到此处,我们可以在命令行窗口中,输入
     arp –a
来看一下效果,类似于这样的条目
210.118.45.100    00-0b-5f-e6-c5-d7    dynamic
就是我们电脑里存储的关于IP地址与MAC地址的对应关系,dynamic表示是临时存储在ARP缓存中的条目,过 一段时间就会超时被删除(xp/2003系统是2分钟)。
 
这样一来,比如我们的电脑要和一台机器比如210.118.45.1通信的时候,它会首先去检查arp缓存,查找是 否有对应的arp条目,如果没有,它就会给这个以太网络发ARP请求包广播询问210.118.45.1的对应MAC地址, 当然,网络中每台电脑都会收到这个请求包,但是它们发现210.118.45.1并非自己,就不会做出相应,而210.118.45.1就会给我们的电脑 回复一个ARP应答包,告诉我们它的MAC地址是xx-xx-xx-xx-xx-xx,于是我们电脑的ARP缓存就会相应刷 新,多了这么一条:
210.118.45.1   xx-xx-xx-xx-xx-xx   dynamic
 
为什么要有这么一个ARP缓存呢,试想一下如果没有缓存,我们每发一个IP包都要发个广播查询地址,岂不是又浪费带 宽又浪费资源?
     而且我们的网络设备是无法识别ARP包的真伪的,如果我们按照ARP的格式来发送数据包,只要信息有效计算机就会根据包中的内容做相应的反应.
 
试想一下,如果我们按照ARP响应包的相应的内容来刷新自己的ARP缓存中的列表,嘿嘿,那我们岂不是可以根据这点 在没有安全防范的网络中玩些ARP包的小把戏了?在后面的文章里我就手把手来教你们如何填充发送ARP包,不过先别急,我们再继续学点基础知识^_^
 
2.ARP包的格式
 
     既然我们要来做一个我们自己的ARP包,当然首先要学习一下ARP包的格式。
 
     从网络底层看来,一个ARP包是分为两个部分的,前面一个是物理帧头,后面一个才是ARP
         首先,物理帧头,它将存在于任何一个协议数据包的前面,我们称之为DLC Header,因为这个帧头是在数据链路层构造的,并且其主要内容为收发双方的物理地址,以便硬件设备识别。 
DLC Header
字段
长度(Byte)
默认值
备注
接收方MAC
6
 
广播时,为 ff-ff-ff-ff-ff-ff
发送方MAC
6
 
 
Ethertype
2
0x0806
0x0806是ARP帧的类型值
                           图1 物理帧头格式
 
     图1是需要我们填充的物理帧头的格式,我们可以看到需要我们填充的仅仅是发送端和接收端的物理地址罢了,是不是很简单呢?
      接下来我们看一下ARP帧的格式. 
ARP Frame
字段
长度(Byte)
默认值
备注
硬件类型
2
0x1
以太网类型值
上层协议类型
2
0x0800
上层协议为IP协议
MAC地址长度
1
0x6
以太网MAC地址长度为 6
IP地址长度
1
0x4
IP地址长度为 4
操作码
2
 
0x1表示ARP请求包,0x2表示应答包
发送方MAC
6
 
 
发送方IP
4
 
 
接收方MAC
6
 
 
接收方IP
4
 
 
填充数据
18
 
因为物理帧最小长度为64字节,前面的42字节再加上4个CRC校验字节,还差18个字节
                             图2 ARP帧格式
 
我们可以看到需要我们填充的同样也只是MAC,IP,再加上一个1或2的操作码而已。
 
3.ARP包的填充
1) 请求包的填充:
     比如我们的电脑MAC地址为 aa-aa-aa-aa-aa-aa,IP为 192.168.0.1
 我们想要查询 192.168.0.99的MAC地址,应该怎么来做呢?
 
     首先填充DLC Header,通过前面的学习我们知道,想要知道某个计算机对应的MAC地址是要给全网发送广播的,所以接收方MAC肯定是 ffffffffffff,发送方MAC当然是自己啦,于是我们的DLC Header就填充完成了,如图,加粗的是我们要手动输入的值(当然我编的程序比较智能,会根据你选择的ARP包类型帮你自动填入一些字段,你一用便知 ^_^)。
 
DLC Header
字段
长度(Byte)
填充值
接收方MAC
6
ffffffffffff
发送方MAC
6
aaaaaaaaaaaa
Ethertype
2
0x0806
图3 ARP请求包中 DLC Header内容
 
    接下来是ARP帧,请求包的操作码当然是 1,送方的MAC以 及IP当然填入我们自己的,然后要注意一下,这里的接收方IP填入我们要查询的那个IP地址,就是192.168.0.99 了,而接收方MAC填入任意值就行,不起作用,于是,如图,
 
                            ARP Frame
字段
长度(Byte)
填充值
硬件类型
2
1
上层协议类型
2
0800
MAC地址长度
1
6
IP地址长度
1
4
操作码
2
1
发送方MAC
6
aaaaaaaaaaaa
发送方IP
4
192.168.0.1
接收方MAC
6
任意值 xxxxxxxxxxxx
接收方IP
4
192.168.0.99
填充数据
18
0

 

                 图4 ARP请求包中 ARP帧的内容
 
    如果我们构造一个这样的包发送出去,如果 192.168.0.99存在且是活动的,我们马上就会收到一个192.168.0.99发来的一个响应包,我们可以查看一下我们的ARP缓存列表,是不 是多了一项类似这样的条目:           
       192.168.0.99                  bb-bb-bb-bb-bb-bb
     是不是很神奇呢?
     我们再来看一下ARP响应包的构造
 
2) 响应包的填充
     有了前面详细的解说,你肯定就能自己说出响应包的填充方法来了吧,所以我就不细说了,列两个表就好了
 
     比如说给 192.168.0.99(MAC为 bb-bb-bb-bb-bb-bb)发一个ARP响应包,告诉它我们的MAC地址为 aa-aa-aa-aa-aa-aa,就是如此来填充各个字段
 
DLC Header
字段
长度(Byte)
填充值
接收方MAC
6
bbbbbbbbbbbb
发送方MAC
6
aaaaaaaaaaaa
Ethertype
2
0x0806
              图5 ARP响应包中 DLC Header内容
 
ARP Frame
字段
长度(Byte)
填充值
硬件类型
2
1
上层协议类型
2
0800
MAC地址长度
1
6
IP地址长度
1
4
操作码
2
2
发送方MAC
6
aaaaaaaaaaaa
发送方IP
4
192.168.0.1
接收方MAC
6
bbbbbbbbbbbb
接收方IP
4
192.168.0.99
填充数据
18
0
          图6 ARP响应包中 ARP帧的内容
 
    这样192.168.0.99的ARP缓存中就会多了一条关于我们192.168.0.1的地址映射。
    好了,终于到了编程实现它的时候了^_^
二.  发送ARP包的编程实现
1.        填充数据包
上面的那些关于ARP包各个字段的表格,对应在程序里就是结构体,对应于上面的表格,于是我们需要三个下面这样的结 构体
// DLC Header
typedef struct tagDLCHeader                    
{
   unsigned char      DesMAC[6];             /* destination HW addrress */
   unsigned char      SrcMAC[6];             /* source HW addresss */
   unsigned short     Ethertype;                /* ethernet type */
} DLCHEADER, *PDLCHEADER;
// ARP Frame
typedef struct tagARPFrame                     
{
          unsigned short         HW_Type;           /* hardware address */
          unsigned short         Prot_Type;             /* protocol address */
          unsigned char      HW_Addr_Len;       /* length of hardware address */
          unsigned char      Prot_Addr_Len;         /* length of protocol address */
          unsigned short         Opcode;                /* ARP/RARP */
 
          unsigned char      Send_HW_Addr[6];     /* sender hardware address */
          unsigned long      Send_Prot_Addr;      /* sender protocol address */
          unsigned char      Targ_HW_Addr[6];     /* target hardware address */
          unsigned long      Targ_Prot_Addr;      /* target protocol address */
          unsigned char      padding[18];
} ARPFRAME, *PARPFRAME;
// ARP Packet = DLC header + ARP Frame
typedef struct tagARPPacket                
{
     DLCHEADER     dlcHeader;
     ARPFRAME      arpFrame;
} ARPPACKET, *PARPPACKET;
 
这些结构体一定能看懂吧,在程序中就是对号入座就好了
1.        填充数据包
 
下面我举个填充包头的例子,我首先定义个了一个转换字符的函数,如下
 
/****************************************************************************
 *   Name & Params::
 *             formatStrToMAC
 *             (
 *                 const LPSTR lpHWAddrStr : 用户输入的MAC地址字符串
 *                 unsigned char *HWAddr :   返回的MAC地址字符串(赋给数据包结构体)
 *             )
 *   Purpose:
 *             将用户输入的MAC地址字符转成数据包结构体需要的格式
 ****************************************************************************/
void formatStrToMAC(const LPSTR lpHWAddrStr, unsigned char *HWAddr)
{
       unsigned int i, index = 0, value, temp;
      unsigned char c;
 
      _strlwr(lpHWAddrStr);                                                   // 转换成小写
 
      for (i = 0; i < strlen(lpHWAddrStr); i++)
     {
           c = *(lpHWAddrStr + i);
            if (( c>=’0’ && c<=’9’ ) || ( c>=’a’ && c<=’f’ ))
           {
               if (c>=’0’ && c<=’9’)  temp = c - ’0’;                         // 数字
               if (c>=’a’ && c<=’f’)  temp = c - ’a’ + 0xa;               // 字母
               if ( (index % 2) == 1 )
              {
                   value = value*0x10 + temp;
                   HWAddr[index/2] = value;
              }
              else value = temp;
              index++;
         }
               if (index == 12) break;
        }
}
 
// 开始填充各个字段
ARPPACKET ARPPacket;                                                  // 定义ARPPACKET结构体变量
 
    memset(&ARPPacket, 0, sizeof(ARPPACKET));                      // 数据包初始化
 
     formatStrToMAC(“DLC源MAC字符串”,ARPPacket.dlcHeader.SrcMAC);       // DLC帧头
     formatStrToMAC(“DLC目的MAC字符串”,ARPPacket.dlcHeader.DesMAC);
 
     formatStrToMAC(“ARP源MAC字符串”,ARPPacket.arpFrame.Send_HW_Addr);  // 源MAC
     ARPPacket.arpFrame.Send_Prot_Addr = inet_addr(srcIP);              // 源IP
     formatStrToMAC(“ARP目的MAC字符串”,ARPPacket.arpFrame.Targ_HW_Addr); // 目的MAC
     ARPPacket.arpFrame.Targ_Prot_Addr = inet_addr(desIP);               // 目的IP
    
     ARPPacket.arpFrame.Opcode = htons((unsigned short)arpType);        // arp包类型
    
     // 自动填充的常量
     ARPPacket.dlcHeader.Ethertype = htons((unsigned short)0x0806); // DLC Header的以太网类型
     ARPPacket.arpFrame.HW_Type = htons((unsigned short)1);           // 硬件类型
     ARPPacket.arpFrame.Prot_Type = htons((unsigned short)0x0800);    // 上层协议类型
     ARPPacket.arpFrame.HW_Addr_Len = (unsigned char)6;                 // MAC地址长度
     ARPPacket.arpFrame.Prot_Addr_Len = (unsigned char)4;               // IP地址长度
 
That’s all ! ^_^
填充完毕之后,我们需要做的就是把我们的ARPPACKET结构体发送出去
 
2.发送ARP数据包:
 
我们发送ARP包就要用到winpcap的api了,具体步骤及函数是这样的,为了简单易懂,我把错误处理的地方都 去掉了,详见代码
/**********************************************************************
*    Name & Params::
*             SendARPPacket()
*    Purpose:
*             发送ARP数据包
*    Remarks:
*             用的是winpcap的api函数
***********************************************************************/
void SendARPPacket()
{
     char *AdapterDeviceName =GetCurAdapterName();     // 首先获得获得网卡名字
 
     lpAdapter = PacketOpenAdapter(AdapterDeviceName);     // 根据网卡名字打开网卡
 
     lpPacket = PacketAllocatePacket();               // 给PACKET结构指针分配内存
 
     PacketInitPacket(lpPacket, &ARPPacket, sizeof(ARPPacket)); //初始化PACKET结构指针
                                             // 其中的ARPPacket就是我们先前填充的ARP包
 
     PacketSetNumWrites(lpAdapter, 1);               // 每次只发送一个包
 
     PacketSendPacket(lpAdapter, lpPacket, true)       // Send !!!!! ^_^
 
     PacketFreePacket(lpPacket);                     // 释放资源
     PacketCloseAdapter(lpAdapter);
}
 
呵呵,至此,关于ARP包最关键的部分就讲完了,你现在就可以来随心所欲的发送自己的ARP包了
 
既然作为一篇“科普文章”,接下来我再讲一讲与整个项目有关的附加步骤以及说明。

下面我们来用ARP包玩一些小把戏 ^_^。
四.ARP包的游戏
既然我们可以自己来填充数据包,那么来玩些ARP的“小游戏”欺骗就是易如反掌了,当然,是在没有安全防护的网络里 ,比如只有hub或者交换机把你们相连,而没有路由分段……^_^
下面我就由浅入深的讲一些介绍一些关于ARP的小伎俩。
1. 小伎俩
1)       你可以试着发一个请求包广播,其中的ARP帧里关于你的信息填成这样:
(为了节省篇幅,我只写需要特别指出的填充字段)
发送方MAC
6
随便乱填一个错误的
发送方IP
4
 填上你的IP
   出现什么结果?是不是弹出一个IP地址冲突的提示?呵呵,同样的道理,如果发送方IP填成别人的,然后每隔1秒发一次………..-_-b
 
2)       比如你们都靠一个网关192.168.0.1 上网 ,如果你想让192.168.0.77 上不了网,就可以伪装成网关给192.168.0.77发一个错误的ARP响应包, like this
发送方MAC
6
随便乱填一个错误的
发送方IP
4
 网关IP 192.168.0.1
接收方就填192.168.0.77的相关信息,发送之后,它还能上网不?
这样能折腾他好一阵子了,只要它的系统得不到正确的到网关的ARP映射表它就一直上不了网了 ^_^
          呵呵类似的伎俩还有很多,不过只停留在这点东西上也没什么意思,还是看看稍微高深一点的吧^_^

      2.        ARP欺骗
因为在以太网里,网络设备就是靠MAC信息来识别的计算机的,比如A电脑知道MAC地址为 22-22-22-22-22-22的电脑是B,而如果我给A发送一个ARP响应包,告诉它我的MAC是22-22-22-22-22-22的话,A同样 会认为我的计算机是B了,那么好,我们设想有这么一个环境,
A的防火墙只对IP为192.168.0.2 MAC为 22-22-22-22-22-22的B有信任关系,而且A打开了21端口提供FTP服务,正常情况下因为防火墙的缘故我们的计算机是连不到A的,
于是我们想办法让B down掉,或者在它关机的时候,我们把我们的IP改成B的192.168.0.2,然后给A发送一个ARP回应包,告诉A更新一下ARP缓存列 表,192.168.0.2的IP映射到我们的
MAC地址上来,于是,奇迹出现了,我们可以连到A的FTP上了,防火墙失效了^_^
不过这个办法只能在同网段内生效,如果我们和A不在一个网段内,那就要复杂的多了,还要配合ICMP的重定向来控制 报文的路由,这个我准备在以后阐述ICMP包的时候, 详细讲解,就不再此多说了。
    3.        基于ARP欺骗的监听原理
监听的技术有很多了,不过我们常用的sniffer工具只能在基于hub的网络中起作用,碰到哪怕是交换机都无能为 力了,这个时候我们的ARP欺骗技术就派上用场了。
还是假设有三台主机A,B,还有我们的主机,位于同一个交换式局域网中
A与B正在通信,如果我们想要刺探A――>B通信的内容,于是我们就可以给A发送一个伪造的ARP回应包,告 诉A,B的IP对应的MAC条目为我们的MAC地址,于是,A也就会相应的刷新自己的ARP缓存,将, 发给B的数据,源源不断的发送到我们的主机上来,这样我就可以对接收到的数据包进行分析就好了,达到了监听的目的。当然,因为动态ARP缓存是动态的,有 超时时间的,所以我们必须每隔一段时间就给A发送一个ARP回应包
虽然我们这样达到了目的, ,但是A到B的通信却被停止了,为了不让B发现,我们还要对每次接收到的数据包进行转发,全部都转发给B,这样就天衣无缝了^_^
同样的,如果我们还想监听B à A的数据包,一样给B发一个ARP回应包,告诉B,A的IP对应的MAC是我们的主机MAC,于是B到A的数据包也源源不断的发到我们的主机上来了,当然 我们也是一样要对这些数据包进行转发,如图:
A  <------>   我们的主机   <------>   B
一切都无误的话,A和B的通信内容就这样不知不觉的被我们监听到了^_^
具体的代码实现由于篇幅的关系我就不放在这里讲了,如果需要我就专门另写篇文章附上完整代码吧
至此,我们的ARP基础知识就讲完了,但愿您能从中有所收获
 
后记:
 因为本人开发都是使用VC++.net 2003,所以没有安装.net的朋友是打不开工程的,可以试一下vckbase上的工程转换工具,本人没有试过,不保证有效
             
而且本文的代码使用了winpcap开发包,是要另外安装ainpcap驱动。
读者可以安装我代码包里的驱动,不过它更新很快,可以到它主页上去下载最新版本
       
    不做开发的读者,只用下载并安装这个就可以了WinPcap auto-installer (driver +DLLs)
   我的原文及源码下载地址稍后贴出,请关注本帖^_^
   
     源码下载地址,新鲜出炉,非常感谢 _foo 兄弟提供的空间


阅读(1019) | 评论(0) | 转发(0) |
0

上一篇:ARP请求分组格式

下一篇:HB可靠消息通信

给主人留下些什么吧!~~