⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 447.htm

📁 unix高级编程原吗
💻 HTM
📖 第 1 页 / 共 2 页
字号:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>CTerm非常精华下载</title>
</head>
<body bgcolor="#FFFFFF">
<table border="0" width="100%" cellspacing="0" cellpadding="0" height="577">
<tr><td width="32%" rowspan="3" height="123"><img src="DDl_back.jpg" width="300" height="129" alt="DDl_back.jpg"></td><td width="30%" background="DDl_back2.jpg" height="35"><p align="center"><a href="http://apue.dhs.org"><font face="黑体"><big><big>apue</big></big></font></a></td></tr>
<tr>
<td width="68%" background="DDl_back2.jpg" height="44"><big><big><font face="黑体"><p align="center">               ● UNIX网络编程                       (BM: clown)                </font></big></big></td></tr>
<tr>
<td width="68%" height="44" bgcolor="#000000"><font face="黑体"><big><big><p   align="center"></big></big><a href="http://cterm.163.net"><img src="banner.gif" width="400" height="60" alt="banner.gif"border="0"></a></font></td>
</tr>
<tr><td width="100%" colspan="2" height="100" align="center" valign="top"><br><p align="center">[<a href="index.htm">回到开始</a>][<a href="319.htm">上一层</a>][<a href="448.htm">下一篇</a>]
<hr><p align="left"><small>发信人: scz (小四), 信区: Security <br>

标  题: IP分片重组的分析和常见碎片攻击 v0.2 <br>

发信站: 武汉白云黄鹤站 (Sat Sep 16 06:21:06 2000), 站内信件 <br>

作者:yawl <mailto:yawl@nsfocus.com> <br>

主页:http://www.nsfocus.com <br>

日期:v0.1: 2000-07 <br>

      v0.2: 2000-09 <br>

一 前言 <br>

本文对linux的IP组装算法进行了分析,因为IP碎片经常用于DOS等攻击,在文章后面我 <br>

结合 <br>

了一些攻击方法进行了更进一步的说明。内核主要参考版本是2.2.16,另外简要的介绍 <br>

了 <br>

2.4.0-test3中的一些变化. <br>

二 目录 <br>

1- 概述 <br>

2- 关键数据结构 <br>

3- 重要函数说明 <br>

4- 2.4系列的变化 <br>

5- 常见碎片攻击 <br>

1. 概述 <br>

在linux源代码中,ip分片重组的全部程序几乎都在都在\net\ipv4\ip_fragment.c <br>

文件中。其对外提供一个函数接口ip_defrag()。其函数原型如下: <br>

        struct sk_buff *ip_defrag(struct sk_buff *skb) <br>



众所周知,网络数据报在linux的网络堆栈中是以sk_buff的结构传送的,ip_defrag()的 <br>

  <br>

功能就是接受分片的数据包(sk_buff),并试图进行组合,当完整的包组合好时,将新的 <br>

  <br>

sk_buff返还,否则返回一个空指针。 <br>

此函数在其他文件中的调用如下: <br>

ip层接收主函数为ip_rcv()(\net\ipv4\ip_input.c),任何IP包都需经过此函数处理。 <br>

如果此包是发往本机,则调用ip_local_deliver()函数(\net\ipv4\ip_input.c)进行 <br>

  <br>

处理,一般的系统碎片只有在到达最终目的的时候才进行重组(尽管在传输过程中可 <br>

能被进一步分成更小的片)。在ip_local_deliver()中我们可发现如下代码: <br>

if (sysctl_ip_always_defrag == 0 &&           /*编译时未设置提前组装*/ <br>

(iph->frag_off & htons(IP_MF|IP_OFFSET))) {   /*判断是否是分片包*/ <br>

                skb = ip_defrag(skb);         /*条件满足,进行组装*/ <br>

                if (!skb)                     /*若组装好则进行下一步处理,出 <br>

错 <br>

                        return 0;             或仍未组装完返回*/ <br>

                iph = skb->nh.iph;            /*重新定位ip头的指针*/ <br>

} <br>

iph->frag_off只有在设置MF(more fragment)或offset!=0才意味着是分片包,因此 <br>

此处的检验理所当然,但为什么判断sysctl_ip_always_defrag == 0呢? <br>

在看ip_rcv()时我们应该已经注意到在刚进行了版本号,长度,校验和等判断后,有如 <br>



下 <br>

一段代码: <br>

if (sysctl_ip_always_defrag != 0 && <br>

            iph->frag_off & htons(IP_MF|IP_OFFSET)) { <br>

                skb = ip_defrag(skb); <br>

                if (!skb) <br>

                        return 0; <br>

                iph = skb->nh.iph; <br>

                ip_send_check(iph); <br>

} <br>

即如果sysctl_ip_always_defrag==1的话,ip_defrag()的调用位置将有变化,对任何 <br>

进来的IP分片都要进行重组,可以想像,如果此机器作路由器的话,将对所有的分片 <br>

组装好后,才会进行转发。此举一般是没有必要的。这个值可以通过sysctl命令动态 <br>

设置,用sysctl -a可以看到在一般的系统中,此值被设为0: <br>

#sysctl -a <br>

...... <br>

net.ipv4.ip_always_defrag = 0 <br>

...... <br>

2. 关键数据结构(2.2系列) <br>

每一个分片用ipfrag结构表示: <br>

/* Describe an IP fragment. */ <br>

struct ipfrag { <br>



        int             offset;         /* offset of fragment in IP datagram <br>

  <br>

*/ <br>

        int             end;            /* last byte of data in datagram <br>

*/ <br>

        int             len;            /* length of this fragment <br>

*/ <br>

        struct sk_buff  *skb;           /* complete received fragment <br>

*/ <br>

        unsigned char   *ptr;           /* pointer into real fragment data <br>

*/ <br>

        struct ipfrag   *next;          /* linked list pointers <br>

*/ <br>

        struct ipfrag   *prev; <br>

}; <br>

这些分片形成一个双向链表(在linux内核中,若需要使用链表,除非有特殊需要,否则 <br>

推 <br>

荐 <br>

双向链表,见document\CodingStyle),表示一个未组装完的分片队列(属于一个 <br>

ip包)。 <br>

这个链表的头指针要放在ipq结构中: <br>

/* Describe an entry in the "incomplete datagrams" queue. */ <br>



struct ipq { <br>

        struct iphdr    *iph;           /* pointer to IP header <br>

*/ <br>

        struct ipq      *next;          /* linked list pointers <br>

*/ <br>

        struct ipfrag   *fragments;     /* linked list of received fragments <br>

  <br>

*/ <br>

        int             len;            /* total length of original datagram <br>

  <br>

*/ <br>

        short           ihlen;          /* length of the IP header <br>

*/ <br>

        struct timer_list timer;        /* when will this queue expire? <br>

*/ <br>

        struct ipq      **pprev; <br>

        struct device   *dev;           /* Device - for icmp replies */ <br>

}; <br>

注意每个ipq保留了一个定时器(即struct timer_list timer;)。 <br>

ipq也会形成一个链表,它们是内核当前未组装完的所有IP包。为了便于查找,保留了一 <br>

个 <br>

hash表: <br>

hash表: <br>

#define IPQ_HASHSZ      64 <br>

struct ipq *ipq_hash[IPQ_HASHSZ]; <br>

#define ipqhashfn(id, saddr, daddr, prot) \ <br>

        ((((id) >> 1) ^ (saddr) ^ (daddr) ^ (prot)) & (IPQ_HASHSZ - 1)) <br>

        --------_____________ <br>

        |   1  |             | <br>

        --------        -----------      ------------       ------------ <br>

Hash表  |   2  |        |  ipq1    |---->|  ipfrag1  |----->| ipfrag2  |---- <br>

-->. <br>

...... <br>

        --------        ------------     -------------      ------------ <br>

        ......               | <br>

        --------             \/ <br>

        |  63  |        ------------     -------------      ----------- <br>

        --------        |  ipq2    |---->|  ipfrag1  |----->| ipfrag2 |----- <br>

->.. <br>

..... <br>

                        ------------     -------------      ----------- <br>

                             | <br>

                             \/ <br>

                        ------------     -------------      ----------- <br>

                        |  ipq3    |---->|  ipfrag1  |----->| ipfrag2 |----- <br>



->.. <br>

..... <br>

                        ------------     -------------      ----------- <br>

                             | <br>

                             \/ <br>

                          ........ <br>

每个IP包用如下四元组表示:(id,saddr,daddr,protocol),四个值都相同的碎片保留 <br>

在一 <br>

个IPQ中, <br>

即可组装成一个完整的IP包。 <br>

此结构在2.4内核中有了改动,具体将在下文中声明。 <br>

3. 重要函数说明(2.2系列) <br>

3.1 ip_defrag() <br>

ip_defrag()是整个流程的入口,下面我们首先对ip_defrag()作一定的说明。 <br>

(1)为了防止因保留分片而造成内存消耗过大,linux设置了界限来防止这种情况,如果 <br>

超过 <br>

了 <br>

内存使用的上限,则清空内存中最老的队列(ipq).所用内存的大小保存在变量ip_fra <br>

g_me <br>

m中, <br>

当然,对它的读写都应是“原子”操作(atomic_sub,atomic_add,atomic_read,etc <br>

)。 <br>

)。 <br>

其定义在文件ip_fragment.c前部: <br>

atomic_t ip_frag_mem = ATOMIC_INIT(0);          /* Memory used for fragments <br>

 */ <br>

if (atomic_read(&ip_frag_mem) > sysctl_ipfrag_high_thresh) <br>

                ip_evictor(); <br>

ip_evicator的具体操作将在下文中描述。 <br>

(2)以id, saddr, daddr, protocol为标志检索是否已经建立了相应的ipq,若发现, <br>

则返 <br>

回 <br>

ipq的指针,并重置定时器。 <br>

qp = ip_find(iph, skb->dst); <br>

(3)此时有一个if/else对,其作用是: <br>

如果ipq已经存在,则证明已经有同一个包的其他分片到达。检查此片是不是第一个分片 <br>

( <br>

因为 <br>

分片到达顺序可能错乱),若是,将ip头信息和头长度保留在ipq结构中(); <br>

  <br>

if (offset == 0) { <br>

/* Fragmented frame replaced by unfragmented copy? */ <br>

if ((flags & IP_MF) == 0) <br>

        goto out_freequeue; <br>

qp->ihlen = ihl; <br>



memcpy(qp->iph, iph, (ihl + 8)); <br>

} <br>

如果不存在,当然要建立一个了: <br>

qp = ip_create(skb, iph); <br>

if (!qp) <br>

        goto out_freeskb; <br>

ip_create便是分配出一块内存,初始化这个ipq,并在hash表中登记。 <br>

到此为止ipq已经肯定存在了,不管是已经存在的,还是我们刚才生成的。 <br>

(4)对包的长度进行检测,如果超过了ip包的最大范围,则报警,并丢弃此包。jolt2 <br>

  <br>

便是利用这点将window系统打瘫的。由于linux做了这种检查,所以基本免受其害。 <br>

(5)调节end值(数据的结尾位置),如果是最后一个包,则最终整个ip包的长度便可 <br>

以知 <br>

道了,为了组装时方便,将其记录到ipq中。 <br>

/* Determine the position of this fragment. */ <br>

end = offset + ntohs(iph->tot_len) - ihl; <br>

/* Is this the final fragment? */ <br>

if ((flags & IP_MF) == 0) <br>

                qp->len = end; <br>

(6)接下来很长一段代码(line481-line586)便是定位这份分片在整个数据包中的位 <br>

置。 <br>

如果分片之间有重合(恶意攻击和其他异常),则能归并便归并。这个问题我们将在后 <br>



面 <br>

(常见碎片攻击中)详谈。 <br>

(7)此时我们已经知道这个分片的具体位置了。我们要生成一份新的ipfrag结构,并将 <br>

其 <br>

放到 <br>

我们刚才找到的正确位置上去。 <br>

tfp = ip_frag_create(offset, end, skb, ptr); <br>

if (!tfp) <br>

        goto out_freeskb; <br>

/* Insert this fragment in the chain of fragments. */ <br>

tfp->prev = prev; <br>

tfp->next = next; <br>

if (prev != NULL) <br>

        prev->next = tfp; <br>

else <br>

        qp->fragments = tfp; <br>

if (next != NULL) <br>

        next->prev = tfp; <br>

(8)ip_done函数检查是否所有的分片已经到齐,如果到齐,则将其组装成一个新的sk <br>

_buf <br>

f <br>

(调用ip_glue),并最终返回到调用ip_defrag的地方。 <br>



if (ip_done(qp)) {       /*全部到齐了么?*/ <br>

        /* Glue together the fragments. */ <br>

        skb = ip_glue(qp); <br>

        /* Free the queue entry. */ <br>

out_freequeue: <br>

        ip_free(qp);    /*原有的ipq结构已经不需要了,释放。*/ <br>

out_skb: <br>

        return skb;     /*组装完成,可以返回了*/ <br>

} <br>

如果没有到齐,则返回NULL. <br>

至此全部组装过程结束。 <br>

3.2 ip_evictor() <br>

当分片所用的内存超过一定的上限时(sysctl_ipfrag_high_thresh)会调用ip_evicator <br>

以释 <br>

放内存。 <br>

ip_evicator会找寻可清空的IPQ,并将其清空,直到到达到可用的下限(sysctl_ipfra <br>

g_lo <br>

w_thresh) <br>

。 <br>

这个值在ip_fragment.c中按如下定义: <br>

int sysctl_ipfrag_high_thresh = 256*1024; <br>

int sysctl_ipfrag_low_thresh = 192*1024; <br>



同样,用sysctl -a可可看到这两参数,同时可以动态修改。 <br>

#sysctl -a <br>

...... <br>

net.ipv4.ipfrag_low_thresh = 196608 <br>

net.ipv4.ipfrag_high_thresh = 262144 <br>

...... <br>

理论上ip_evicator应该采用LRU算法,将最古老的IPQ清除。但目前linux(包括2.4.0)没 <br>

有 <br>

实现此功能 <br>

,只是将hash表按次序清空,这样的好处是简单易行。 <br>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -