如果我们构造一个这样的包发送出去,如果 192.168.0.99存在且是活动的,我们马上就会收到一个192.168.0.99发来的一个响应包,我们可以查看一下我们的ARP缓存列表,是不是多了一项类似这样的条目:
比如说给 192.168.0.99(MAC为 bb-bb-bb-bb-bb-bb)发一个ARP响应包,告诉它我们的MAC地址为 aa-aa-aa-aa-aa-aa,就是如此来填充各个字段
好了,终于到了编程实现它的时候了^_^
二. 发送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 Helper的api,所以要包含以及库文件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;
}
}
这样一来,我们基本上大功告成了,其他还有一些东西在这里就不讲了,大家可以下载我的代码看看就好了。
下面我们来用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)
我的原文及源码下载地址稍后贴出,请关注本帖^_^