В открытом доступе появился фреймворк ICMP-Ghost, предназначенный для скрытого управления заражёнными устройствами. Проект написан на ассемблере для архитектуры x64. Его автор утверждает, что инструмент способен обходить средства защиты конечных точек, а также сетевые системы обнаружения и предотвращения вторжений (IDS/IPS). По заявлению разработчика, фреймворк отличается низким потреблением памяти и высокой производительностью. При этом, несмотря на название, он поддерживает два канала связи: на базе ICMP-эхо-запросов и DNS-запросов. Исходный код размещён в открытом репозитории, поэтому воспользоваться им может любой желающий.
Описание
Канал на основе ICMP маскируется под обычные эхо-запросы и эхо-ответы. В каждом пакете помимо управляющих данных размещается временная метка, служебный заполнитель и зашифрованный блок информации. Данные шифруются с использованием ключа, который меняется для каждого следующего байта, что затрудняет прямое извлечение содержимого. Для защиты от подмены используется асимметричная аутентификация. Она основана на математической связи между идентификатором и порядковым номером пакета. Если сумма значений не совпадает с заданным числом, пакет не принимается. Благодаря этому клиент и сервер распознают только собственные сообщения. Заполнитель внутри пакета имитирует стандартное заполнение, встречающееся в ICMP-трафике операционных систем семейства Linux.
Второй канал реализован через DNS-туннелирование, однако это не классическая схема. Вместо обмена запросами и ответами между узлами передаются только запросы. Вся полезная нагрузка помещается в первый сегмент доменного имени. Длина этого сегмента ограничена 56 символами. Для кодирования данных используется обычный алфавит из строчных букв и цифр. Как показало исследование Netomize, в качестве прикрытия подставляются домены крупных известных ресурсов. Благодаря этому запросы внешне выглядят как легитимные обращения к популярным сайтам. Направление передачи определяется различием в порядке сжатия и шифрования данных.
Несмотря на усилия по маскировке, структура пакетов содержит устойчивые признаки, позволяющие отличить их от легитимного трафика. К ним относятся фиксированные значения некоторых полей, постоянные заполнители и жёстко заданные соотношения между номерами. Кроме того, данные внутри пакетов преобразуются по одному и тому же алгоритму, что также создаёт характерный почерк. Совокупность этих особенностей даёт возможность автоматически выделять трафик фреймворка из общего потока. Причём для детектирования не требуется анализировать содержимое зашифрованных блоков - достаточно контролировать служебные поля и их взаимосвязи.
На основе выявленных признаков подготовлен набор правил для платформы Yara-X, предназначенной для поиска вредоносных проявлений в сетевых данных. Правила разделены на три группы: для ICMP-запросов, для ICMP-ответов и для DNS-запросов. В каждой группе учитываются не только внешние характеристики пакетов, но и внутренние связи между полями. Такой подход позволяет сократить количество ложных срабатываний. В случае DNS-канала отдельно проверяется длина имени, символьный состав и отсутствие признаков стандартного ответа. При необходимости правила можно адаптировать под любой порт, поскольку структура UDP-пакетов, используемых фреймворком, остаётся стабильной. Готовые правила доступны в открытом репозитории и подходят для анализа записанного трафика.
Разработчики правил подчёркивают, что составленные сигнатуры не являются единственным средством защиты. Они рассчитаны на использование в системе мониторинга, где сетевой трафик непрерывно проверяется в реальном времени. Поскольку фреймворк использует фиксированные параметры для аутентификации пакетов, правило может срабатывать даже на начальной стадии взаимодействия, когда ещё не передан ни один блок данных.
Дополнительно была проведена проверка с помощью встроенного модуля анализа ICMP-трафика. Он изучает поведенческие характеристики потока: частоту следования пакетов, их размер, последовательность номеров и соотношение запросов с ответами. На тестовых данных модуль зафиксировал несколько подозрительных признаков. В частности, пакеты оказались аномально крупными, частота отправки превышала обычные значения, а один из потоков начинался с ответного пакета. Эти отклонения характерны для туннельных протоколов, которые вписывают большие объёмы данных в служебные сообщения. Поведенческий анализ тем самым дополняет сигнатурный поиск.
Для бизнеса появление подобных инструментов означает, что даже обычные протоколы ICMP и DNS могут использоваться для скрытого управления системой. Это важно для компаний, которые не контролируют служебный трафик. В сетях, где разрешён широкий диапазон ICMP-сообщений и DNS-запросов к любым доменам, злоумышленники получают канал для передачи команд и вывода данных. Поэтому при построении защиты стоит ограничивать доступ к внешним DNS-серверам, следить за объёмом ICMP-трафика и использовать системы, которые анализируют поведение сетевых узлов.
При обнаружении подозрительной активности, похожей на ICMP-Ghost, следует немедленно изолировать затронутый узел от сети. Затем необходимо собрать образы памяти и сетевые журналы для последующего анализа. Наличие готовых сигнатур позволяет быстро подтвердить или опровергнуть факт использования фреймворка в инциденте. Важно помнить, что злоумышленники могут модифицировать исходный код и изменить часть констант, поэтому сигнатурные методы желательно дополнять поиском аномалий.
Проект ICMP-Ghost показателен сразу с двух точек зрения. С одной стороны, он иллюстрирует тенденцию перехода вредоносного кода на низкоуровневые языки для обхода защитных механизмов. Ассемблер позволяет уменьшить размер исполняемого файла, скрыть логику работы и затруднить обратную разработку. С другой стороны, пример показывает, что даже сложные схемы туннелирования оставляют следы, пригодные для автоматического распознавания. Стандартные поля сетевых протоколов накладывают ограничения, которые можно выявить. Поэтому надежды на полную невидимость таких инструментов пока не подтверждаются.
Индикаторы компрометации
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | rule icmp_ghost_implant_icmp_echo_request_c2_detection { meta: description = "Detect ICMP-Ghost ICMP Echo Request C2 traffic" reference = """https://github.com/JM00NJ/ICMP-Ghost-A-Fileless-x64-Assembly-C2-Agent https://netacoding.com/posts/icmp-ghost/ """ filter = "Frames (frames:)" author = "Netomize" date = "08/11/2026" strings: $padding = { 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f } condition: ip4.is_set and not ip4.in_ip6 and icmp4.is_set and icmp4.type == 0x08 and icmp4.code == 0x00 and with ident_num = uint16be(icmp4.data.offset), seq_num = uint16be(icmp4.data.offset+2), idata = icmp4.data.offset + 4, isize = icmp4.data.size : ( // data is rolling XOR encrypted isize >= 28 + 56 // (28 -> ident, seq, rdtsc value and the padding bytes) + (56 -> chunk size) and math.in_range(ident_num, 10000, 29999) and math.in_range(seq_num, 15001, 35000) and (ident_num + seq_num == 45000) // asymmetric authentication and uint32be(idata + 4) == 0x00000000 // rdtsc high 32-bits and $padding at (idata + 8) // + 8 -> skipping over the rdtsc value ) } |
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | rule icmp_ghost_implant_icmp_echo_reply_ctrl_cmd_c2_detection { meta: description = "Detect ICMP-Ghost ICMP Echo Reply C2 control command traffic" reference = """https://github.com/JM00NJ/ICMP-Ghost-A-Fileless-x64-Assembly-C2-Agent https://netacoding.com/posts/icmp-ghost/ """ filter = "Frames (frames:)" author = "Netomize" date = "08/11/2026" strings: $padding = { 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f } condition: ip4.is_set and not ip4.in_ip6 and icmp4.is_set and icmp4.type == 0x00 and icmp4.code == 0x00 and with ident_num = uint16be(icmp4.data.offset), seq_num = uint16be(icmp4.data.offset+2), idata = icmp4.data.offset + 4, isize = icmp4.data.size : ( isize > 28 + 10 // data is rolling XOR encrypted and VESQER compressed and math.in_range(ident_num, 10000, 29999) and math.in_range(seq_num, 25001, 45000) and ident_num + seq_num == 55000 // asymmetric authentication and uint32be(idata + 4) == 0x00000000 // rdtsc high 32-bits and $padding at (idata + 8) // + 8 -> skipping over the rdtsc value ) } |
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | rule icmp_ghost_implant_dns_query_c2_detection { meta: description = "Detect ICMP-Ghost DNS Query C2 control command traffic" reference = """https://github.com/JM00NJ/ICMP-Ghost-A-Fileless-x64-Assembly-C2-Agent https://netacoding.com/posts/icmp-ghost/ """ filter = "Frames (frames:)" author = "Netomize" date = "08/11/2026" condition: udp.is_set and dns.is_set and ((dns.id >> 8) + (dns.id & 0xff)) == 0xff // asymmetric authentication and not dns.flag.response and dns.flag.opcode == 0 and not dns.flag.truncated and dns.flag.recdesired and not dns.flag.z and not dns.flag.authenticated and dns.count.queries == 1 and dns.count.ansr_rr == 0 and dns.count.auth_rr == 0 and dns.count.addi_rr == 0 and // Type: A (1) (Host Address); Class: IN (0x0001) dns.qry[0].type == 1 and dns.qry[0].class == 1 and // ex., mjffavtomskxfamakshkfhfevk33qb6cz3ko7yxl6ak74fim3amsekae.github.com with qry_name = dns.qry[0].name, nlabels = qry_name.labels.total : ( // minimum query name length (worst-case scenario) // and number of labels/segments > 2 string.length(qry_name.qname) > 60 and nlabels > 2 and with first_seg = qry_name.labels.segments[0]: ( string.length(first_seg) == 56 and first_seg matches /^[a-z2-7]{56}$/ ) ) } |
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | rule icmp_ghost_implant_dns_udp_query_c2_detection { meta: description = "Detect ICMP-Ghost DNS UDP Query C2 control command traffic" reference = """https://github.com/JM00NJ/ICMP-Ghost-A-Fileless-x64-Assembly-C2-Agent https://netacoding.com/posts/icmp-ghost/ """ filter = "Frames (frames:)" author = "Netomize" date = "08/11/2026" strings: // flags, questions, answer rrs, authority rrs, additional rrs $dheader = { 01 00 00 01 00 00 00 00 00 00 } $qname_rgx = /[a-z2-7]/ $type_class = { 00 00 01 00 01 } condition: udp.is_set and with udata = udp.data.offset, qname = udp.data.offset + 12, usize = udp.data.size : ( // asymmetric authentication ((uint16be(udata) >> 8) + (uint16be(udata) & 0xff)) == 0xff and $dheader at (udata + 2) and // get qname length uint8(qname) == 56 and // check subdomain character set $qname_rgx in (qname + 1 .. qname + 1 + uint8(qname)) and // check end of label, type and class in reverse $type_class at (udata + usize - 5) ) } |