Chinaunix首页 | 论坛 | 博客
  • 博客访问: 160059
  • 博文数量: 31
  • 博客积分: 2025
  • 博客等级: 大尉
  • 技术积分: 380
  • 用 户 组: 普通用户
  • 注册时间: 2009-10-09 15:21
文章分类

全部博文(31)

文章存档

2011年(1)

2010年(1)

2009年(29)

我的朋友

分类: LINUX

2009-10-12 15:23:13

   

         首先要感谢网络安全资深专家卢湖川博士以及VC网络版的limin朋友提供的资料以及帮助^_^

经常看到论坛有人问起关于数据包的截获、分析等问题,幸好本人也对此略有所知,所以就想写一系列的文章来详细深入的探讨关于数据包的知识,,我希望通过这一系列的文章,能使得关于数据包的知识得以普及,所以这系列的每一篇文章我都会有由浅入深的解释、详细的分析、以及编码步骤,另外附上带有详细注释的源码(为了照顾大多数朋友,我提供的都是MFC的源码)

不过由于也是初学者,疏漏之处还望不吝指正。

本文凝聚着笔者心血,如要转载,请指明原作者及出处,谢谢!^_^

 

OK,. Let’s go !  Have fun! q^_^p

 

第一篇         手把手教你玩转ARP

   目录:

    一    关于ARP协议的基础知识

     1.        ARP的工作原理

     2.        ARP包的格式

作者:

    CSDN  VC/MFC 网络编程 PiggyXP  ^_^

 

一.             关于ARP协议的基础知识

1ARP的工作原理

本来我不想在此重复那些遍地都是的关于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包,不过先别急,我们再继续学点基础知识^_^

 

2ARP包的格式

 

     既然我们要来做一个我们自己的ARP包,当然首先要学习一下ARP包的格式。

 

     从网络底层看来,一个ARP包是分为两个部分的,前面一个是物理帧头,后面一个才是ARP

         首先,物理帧头,它将存在于任何一个协议数据包的前面,我们称之为DLC Header,因为这个帧头是在数据链路层构造的,并且其主要内容为收发双方的物理地址,以便硬件设备识别。 

DLC Header

字段

长度(Byte)

默认值

备注

接收方MAC

6

 

广播时, ff-ff-ff-ff-ff-ff

发送方MAC

6

 

 

Ethertype

2

0x0806

0x0806ARP帧的类型值

                           物理帧头格式

 

     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字节再加上4CRC校验字节,还差18个字节

                             2 ARP帧格式

 

我们可以看到需要我们填充的同样也只是MAC,IP,再加上一个12的操作码而已。


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包了
 
既然作为一篇“科普文章”,接下来我再讲一讲与整个项目有关的附加步骤以及说明
 
三.附加步骤以及说明
1. 如何在VC中使用winpcap驱动
       虽然winpcap开发包使用起来非常简便,但是前期准备工作还是要费一番功夫的,缺一不可。^_^
       首先就是要安装它的驱动程序了,可以到它的主页下载,更新很快的
     
     下载WinPcap auto-installer (driver +DLLs),直接安装就好了,或者我提供的代码包里面也有。
     希望以后用winpcap作开发的朋友,还需要下载 Developer's pack,解压即可。
    
        然后,需要设置我们工程的附加包含目录为我们下载Developer's pack开发包的Inclulde目录,连接器的附加依赖库设置为Developer's pack的lib目录。
       当然,因为我们的工作比较简单,就是借用winpcap发送数据包而已,所以只用从
winpcap开发包的include文件夹中,拷贝Packet32.h,到我们的工程来,并且包含它就可
以,但是要注意,Packet32.h本身还要包含一个Devioctl.h,也要一并拷贝进来,当然还有运
行库Packet.lib,一共就是需要拷贝3个文件了,如果加入库不用我多说了吧,在工程里面设
置,或者是在需要它的地方加入 #pragma comment(lib, "Packet.lib")了。
 
        整个项目其实可以分为四个部分,填充数据包、发送数据包、枚举系统网卡列表
相关信息以及枚举系统ARP缓存列表,下面我再讲一下如何获得系统的网卡以及ARP
表,这两个部分都要用到IP Helperapi,所以要包含以及库文件Iphlpapi.lib,
其实都是很简单的,只用寥寥几行就OK了
2.     枚举系统网卡以及信息
最好是先定义关于网卡信息的一个结构体,这样显得结构比较清晰
// 网卡信息
typedef struct tagAdapterInfo         
{
              char szDeviceName[128];           // 名字
              char szIPAddrStr[16];             // IP
              char szHWAddrStr[18];             // MAC
              DWORD dwIndex;                    // 编号         
}INFO_ADAPTER, *PINFO_ADAPTER;
 
/*********************************************************************
*    Name & Params::
*             AddAdapInfoToList
*             (
*                  CListCtrl& list :  CARPPlayerDlg传入的list句柄
*             )
*    Purpose:
*             获得系统的网卡信息,并将其添加到list控件中
*    Remarks:
*             获得网卡IP及MAC用到了IpHelper api GetAdaptersInfo
******************************************************************/
void AddAdapInfoToList(CListCtrl& list)
{
     char tempChar;
     ULONG uListSize=1;
     PIP_ADAPTER_INFO pAdapter;           // 定义PIP_ADAPTER_INFO结构存储网卡信息
     int nAdapterIndex = 0;
 
     DWORD dwRet = GetAdaptersInfo((PIP_ADAPTER_INFO)&tempChar, &uListSize);//关键函数
 
     if (dwRet == ERROR_BUFFER_OVERFLOW)
     {
  PIP_ADAPTER_INFO pAdapterListBuffer = (PIP_ADAPTER_INFO)new(char[uListSize]);
  dwRet = GetAdaptersInfo(pAdapterListBuffer, &uListSize);
  if (dwRet == ERROR_SUCCESS)
  {
     pAdapter = pAdapterListBuffer;
     while (pAdapter)                                              // 枚举网卡然后将相关条目添加到List中
     {
        // 网卡名字
          CString strTemp = pAdapter->AdapterName;                    
          strTemp = "\\Device\\NPF_" + strTemp;                        // 加上前缀
          list.InsertItem(nAdapterIndex,strTemp);                 
          strcpy(AdapterList[nAdapterIndex].szDeviceName,strTemp);
          // IP
          strcpy(AdapterList[nAdapterIndex].szIPAddrStr,
                                                 pAdapter->IpAddressList.IpAddress.String );
          list.SetItemText(nAdapterIndex,1,AdapterList[nAdapterIndex].szIPAddrStr);
          // MAC
          formatMACToStr( AdapterList[nAdapterIndex].szHWAddrStr, pAdapter->Address );
          list.SetItemText(nAdapterIndex,2,AdapterLis[nAdapterIndex].szHWAddrStr);
          // 网卡编号
          AdapterList[nAdapterIndex].dwIndex = pAdapter->Index;         
 
          pAdapter = pAdapter->Next;
          nAdapterIndex ++;
          }
     delete pAdapterListBuffer;
     }
}
}
 
2)获取ARP条目列表
// ARP条目信息
typedef struct tagARPInfo            
{
     char szIPAddrStr[16];              // IP
     char szHWAddrStr[18];             // MAC
     DWORD dwType;                     // 类型
}INFO_ARP, *PINFO_ARP;
 
 
/**********************************************************************
*    Name & Params::
*             AddARPInfoToList
*             (
*                  CListCtrl& list :             CARPPlayerDlg传入的list句柄
*                  const short nAdapterIndex :   用户选中的网卡编号
*             )
*    Purpose:
*             读入系统的ARP缓存列表,.并添加到对话框中
*    Remarks:
*             用到了IpHelper api GetIpNetTable
*             而且用到了WinSock的api,所以要包含
*****************************************************************/
void AddARPInfoToList(CListCtrl& list,const short nAdapterIndex)
{
     char tempChar;
     DWORD dwListSize = 1;
     DWORD dwRet;
     in_addr inaddr;
     list.DeleteAllItems();
 
     dwRet = GetIpNetTable((PMIB_IPNETTABLE)&tempChar, &dwListSize, TRUE);  // 关键函数
     if (dwRet == ERROR_INSUFFICIENT_BUFFER)
     {
         PMIB_IPNETTABLE pIpNetTable = (PMIB_IPNETTABLE)new(char[dwListSize]);
         dwRet = GetIpNetTable(pIpNetTable, &dwListSize, TRUE);
         if (dwRet == ERROR_SUCCESS)
         {
              for (int i=0; i<(int)pIpNetTable->dwNumEntries; i++)
              {
                  // IP
                   inaddr.S_un.S_addr = pIpNetTable->table[i].dwAddr;
                   strcpy( ARPList[i].szIPAddrStr, inet_ntoa(inaddr) );  
                   // MAC
                   formatMACToStr( ARPList[i].szHWAddrStr, pIpNetTable->table[i].bPhysAddr );
                   // Type
                   ARPList[i].dwType = pIpNetTable->table[i].dwType;        
 
                   if (AdapterList[nAdapterIndex].dwIndex != pIpNetTable->table[i].dwIndex)                                                       continue;
 
                   list.InsertItem(i,ARPList[i].szIPAddrStr);
                   list.SetItemText(i,1,ARPList[i].szHWAddrStr);
                   switch(ARPList[i].dwType) {           // 根据type的值来转换成字符显示
                   case 3:
                       list.SetItemText(i,2,"Dynamic");
                       break;
                   case 4:
                       list.SetItemText(i,2,"Static");
                       break;
                   case 1:
                       list.SetItemText(i,2,"Invalid");
                   default:
                       list.SetItemText(i,2,"Other");
                   }
              }
         }
         delete pIpNetTable;
     }
}
        这样一来,我们基本上大功告成了,其他还有一些东西在这里就不讲了,大家可以下载我的代码看看就好了。

阅读(1881) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~