Исследование

Перехватываем «рубильник»: оценка безопасности протокола Zigbee в промышленной среде

Мы ежедневно пользуемся IoT-устройствами и системами домашней автоматизации — от умных колонок до датчиков, автоматически управляющих водяными насосами. Для пользователя такие системы кажутся простыми и понятными, но за ними стоит сложное взаимодействие множества устройств и протоколов.

Одним из таких протоколов является Zigbee. Это беспроводной протокол с низким энергопотреблением на основе стандарта IEEE 802.15.4, который обеспечивает обмен данными между множеством умных устройств. Он широко используется в умных домах, а также на промышленных объектах, где стабильность технологических процессов зависит от скоординированной работы сотен или даже тысяч датчиков.

В Сети можно найти множество руководств по оценке безопасности Zigbee. Большинство из них сосредоточено на домашнем использовании протокола и редко затрагивает его развертывания на промышленных предприятиях.

В этой статье мы рассмотрим безопасность Zigbee. Начнем с основ протокола, обозначим потенциальную поверхность атаки в типичных сценариях использования и разберем два реалистичных вектора, с которыми можно столкнуться на объектах. Затем мы углубимся в технические детали и обсудим пути решения распространенных проблем, возникающих при проведении такой оценки.

Знакомство с Zigbee

Вводные сведения о протоколе

Zigbee — это протокол беспроводной связи, разработанный для создания сетей из устройств с низким энергопотреблением, таких как промышленные датчики. Он основан на стандарте IEEE 802.15.4, который описывает коммуникации маломощных устройств на коротких расстояниях. Zigbee поддерживает ячеистую (mesh) топологию сети: устройства могут связываться по цепочке, что позволяет расширять покрытие сети. Протокол работает в диапазоне 2,4 ГГц и широко используется в умных домах, промышленной автоматизации, системах мониторинга энергии и других областях.

Может возникнуть вопрос: зачем нужен Zigbee, если уже есть Wi-Fi? Все зависит от конкретного применения. В большинстве домов Wi-Fi вполне справляется с подключением устройств. Но что, если у вас есть датчик на батарейке, не подключенный к электросети? Если бы у него был Wi-Fi-модуль, батарея разрядилась бы очень быстро — за несколько дней, потому что Wi-Fi потребляет значительно больше энергии, чем Zigbee, с которым устройства могут работать месяцы, а иногда и годы, не требуя подзарядки.

Представьте более экстремальный сценарий: нужно сбросить с вертолета датчики в зону радиоактивного заражения, куда человек не может попасть, и они должны проработать несколько месяцев без замены батарей. В таких случаях критически важно обращать внимание на фактор энергопотребления — и Zigbee как раз создан для таких задач, где Wi-Fi не подходит.

Если требуется покрыть очень большую площадь — тысячи датчиков на многих тысячах квадратных метров — Zigbee также имеет существенное преимущество: он поддерживает тысячи узлов в ячеистой сети, тогда как Wi-Fi обычно ограничен сотнями устройств.

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

Поскольку и Zigbee, и IEEE 802.15.4 описывают беспроводную связь устройств с низким энергопотреблением, их часто путают. По сути, различие между ними заключается в уровнях сети, которые они поддерживают. IEEE 802.15.4 задает физический уровень (PHY) и уровень управления доступом к среде (MAC) — то есть определяет, как устройства передают и принимают данные с помощью радиосвязи. Zigbee, как и ряд схожих протоколов (Thread, WirelessHART, 6LoWPAN, MiWi), работает поверх IEEE 802.15.4, добавляя свою реализацию сетевого и прикладного уровней.

Они отвечают за формирование сети и взаимодействие устройств.

Zigbee работает в диапазоне 2,4 ГГц, который с ним делят Wi-Fi и Bluetooth. Сеть Zigbee использует 16 каналов с шириной полосы 2 МГц и интервалом 5 МГц между каналами.

Совпадение диапазонов может приводить к помехам от Wi-Fi и Bluetooth, но низкое энергопотребление Zigbee и механизм адаптивного выбора канала помогают минимизировать эти конфликты.

Устройства и сеть

Существует три основных типа устройств Zigbee, каждое из которых выполняет свою роль в сети:

  1. Координатор Zigbee
    Координатор — это «мозг» сети Zigbee. Сеть всегда запускается с координатора.
    В сети может быть только один координатор, который всегда имеет фиксированный адрес — 0x0000.
    Координатор выполняет ряд ключевых задач:

    • запускает сеть Zigbee и управляет ею;
    • выбирает канал Zigbee;
    • назначает адреса другим устройствам;
    • хранит информацию о сети;
    • выбирает PAN ID — уникальный для сети 2-байтовый идентификатор (например, 0x1234);
    • задает Extended PAN ID — 8-байтовое значение, часто представляющее собой ASCII-имя сети.

    У координатора могут быть дочерние устройства: маршрутизаторы или конечные устройства Zigbee.

  2. Маршрутизатор Zigbee
    Маршрутизатор работает аналогично роутеру в традиционной сети: он пересылает данные между устройствами, расширяет радиус действия сети и может иметь дочерние устройства — как правило, конечные устройства Zigbee.Маршрутизаторы особенно важны для построения крупных ячеистых сетей, так как позволяют удаленным узлам обмениваться данными через несколько промежуточных устройств.
  3. Конечное устройство Zigbee
    Конечное устройство или конечная точка — это самый простой и энергоэффективный тип устройства в сети Zigbee. Оно взаимодействует только со своим родительским устройством — координатором или маршрутизатором — и большую часть времени находится в спящем режиме, чтобы экономить энергию. Типичные примеры — датчики, пульты и кнопки.

Конечные устройства не обслуживают дочерние устройства, за исключением случаев, когда они настроены одновременно как маршрутизатор и конечная точка.
Каждое из этих устройств, также называемых узлами Zigbee, имеет два типа адресов:

  • Короткий адрес: 2 байта, аналогично IP-адресу в сети TCP/IP.
  • Расширенный адрес: 8 байт, аналогично MAC-адресу.

Оба адреса могут использоваться на уровнях MAC и сети, в отличие от TCP/IP, где MAC-адрес применяется только на уровне 2, а IP-адрес — на уровне 3.

Тестовая конфигурация

В Zigbee существует множество поверхностей атаки — от фаззинга протокола до низкоуровневых радиоатак — но в этом посте мы сосредоточимся на атаках прикладного уровня. Наш тестовый стенд рассчитан на два вектора атаки и намеренно сделан небольшим для максимальной наглядности.

В нашей тестовой конфигурации координатор Zigbee подключен к единственному устройству, которое одновременно работает как конечная точка и как маршрутизатор. Координатор также имеет другие интерфейсы (Ethernet, Bluetooth, Wi‑Fi и LTE), а к конечной точке подключено реле, которое координатор может включать или выключать через Zigbee. Это происходит в ответ на события, поступающие с любого из интерфейсов (например, Bluetooth‑команда или сообщение по Ethernet).

Наша цель — получить контроль над реле и переключать его состояние (включать и выключать) исключительно через интерфейс Zigbee. В этом сценарии остальные интерфейсы (Ethernet, Bluetooth, Wi‑Fi и LTE) не рассматриваются: вся атака должна работать за счет перехвата Zigbee‑коммуникаций.

В рамках этого исследования мы будем пытаться перехватить обмен данными между конечной точкой и координатором. Мы проверим два вектора атаки:

  1. Инъекция поддельных пакетов: мы сфальсифицируем Zigbee‑команды для активации реле так, словно они поступили от координатора.
  2. Подмена координатора (атака с повторным присоединением): мы создадим подконтрольный нам координатор, который имитирует легитимный. Если удастся заставить конечную точку присоединиться к нему, мы сможем напрямую управлять ею.

Инъекция поддельных пакетов

В этом сценарии предполагается, что сеть Zigbee уже запущена и оба узла (координатор и конечная точка) работают в обычном режиме. Координатор имеет дополнительные интерфейсы, такие как Ethernet, через которые система управляет реле. Например, команда приходит по Ethernet, а координатор отправляет Zigbee‑пакет конечной точке для переключения реле. Наша цель — переключить реле путем инъекции поддельных, но выглядящих легитимными Zigbee‑пакетов, ограничившись только Zigbee‑соединением.

Перехват трафика (сниффинг)

Первый шаг при оценке безопасности протокола беспроводной связи — перехватить трафик и разобраться, как взаимодействуют устройства. Для Zigbee одним из самых распространенных и простых инструментов является USB-модуль nRF52840 производства Nordic Semiconductor. С официальной прошивкой nRF Sniffer для 802.15.4 этот модуль может работать в неизбирательном режиме (promiscuous mode) и перехватывать весь трафик 802.15.4/Zigbee. Можно открыть захваченные данные в Wireshark с помощью соответствующего диссектора и подробно изучить фреймы.

Как определить текущий канал?

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

После выбора канала мы увидим обмен между конечной точкой и координатором, скорее всего, в зашифрованном виде:

Как видно по столбцу Info, Wireshark определяет пакеты лишь как Data или Command без уточнения типа, потому что трафик зашифрован.

Даже если полезная нагрузка Zigbee зашифрована, сетевые и MAC-заголовки остаются видимыми. Это значит, что обычно можно прочитать такие данные, как адрес источника и узла назначения, PAN ID, короткие и расширенные MAC-адреса, а также поля управления фреймом. Полезная нагрузка прикладного уровня (фактическая команда на переключение реле) обычно шифруется на сетевом или прикладном уровне Zigbee, поэтому без ключей шифрования мы не сможем ее прочитать как текст. Тем не менее из одних только заголовков можно получить достаточно полезной информации.

Расшифровка

Zigbee поддерживает несколько типов ключей и разных моделей шифрования. В этой статье для простоты мы рассмотрим ситуацию, в которой задействованы только два устройства: координатор Zigbee и узел, работающий одновременно как конечная точка и маршрутизатор. В таком упрощенном сценарии используется лишь шифрование на уровне сети, тогда как в полноценных ячеистых сетях могут применяться и другие модели.

Шифрование на уровне сети — это очень распространенная концепция. Ранее перехваченный нами трафик зашифрован сетевым ключом (network key). Это симметричный ключ AES-128, общий для всех устройств в сети Zigbee. Он защищает пакеты сетевого уровня (например, маршрутизационные и широковещательные сообщения) на каждом переходе между узлами. Поскольку один и тот же сетевой ключ используется всеми маршрутизаторами в цепочке, такой метод шифрования не считается сквозным (end-to-end).

Для полезной нагрузки прикладного уровня в Zigbee могут применяться два подхода, в зависимости от конкретной реализации:

  • Шифрование на сетевом уровне (на каждом переходе между узлами). Данные APS (Application Support Sublayer), подуровня прикладного уровня Zigbee, шифруются с использованием сетевого ключа. В этом случае каждый маршрутизатор может расшифровывать полезную нагрузку APS. Такой подход не является сквозным шифрованием и не рекомендуется для передачи конфиденциальных данных.
  • Шифрование с использованием связного ключа (сквозное). В этом случае применяется связной ключ (link key) AES-128, общий для двух устройств (например, координатора и конечной точки). Связной ключ обеспечивает сквозную защиту полезной нагрузки APS при передаче между этими двумя устройствами.

Поскольку сетевой ключ позволяет потенциальному атакующему читать и подделывать различные типы сетевого трафика, он должен быть случайным и надежно защищенным. Раскрытие сетевого ключа фактически компрометирует всю сеть.

Когда новое устройство присоединяется к сети, координатор (центр доверия) передает ему сетевой ключ с помощью команды Transport Key. Этот транспортный пакет должен быть защищен связным ключом, чтобы сетевой ключ не передавался в открытом виде. Связной ключ обеспечивает аутентификацию присоединяющегося устройства и безопасную доставку сетевого ключа.

Этот транспортный пакет показан на скриншоте ниже:

Существует два основных способа предоставления связных ключей:

  • Предустановленный ключ — устройство поставляется с кодом установки или предварительно заданным связным ключом.
  • Обмен ключами — устройства используют протокол обмена ключами.

Известной исторической проблемой остается глобальный стандартный связной ключ «ZigBeeAlliance09» для центров доверия. Он использовался в ранних версиях Zigbee (до 3.0) для упрощения тестирования и обеспечения совместимости. Многие производители не отключали его на потребительских устройствах, оставляя серьезную уязвимость: если атакующий знает этот ключ, он может подключиться к устройствам и прочитать или перехватить сетевой ключ.

В более новых версиях Zigbee (3.0 и выше) были введены коды установки и процедуры генерации уникальных связных ключей для каждого устройства. Код установки обычно представляет собой секрет, присваиваемый на заводе (часто он в закодированной форме указывается на наклейке устройства), который центр доверия использует для генерации уникального связного ключа для конкретного устройства. Это позволяет избежать проблем, связанных с использованием одного глобального встроенного ключа.

К сожалению, многие производители по‑прежнему игнорируют эти передовые практики. В реальных условиях при проведении оценки защищенности мы регулярно встречаем устройства, использующие стандартные или жестко заданные ключи.

Как получить эти ключи?

Если конечная точка уже присоединилась к сети и обменивается данными с координатором с помощью сетевого ключа, есть два основных способа расшифровать трафик:

  1. Угадать или подобрать сетевой ключ. Обычно это нереалистично, поскольку корректно сгенерированный сетевой ключ — это случайный ключ AES‑128.
  2. Вынудить устройство повторно присоединиться к сети и перехватить транспортный ключ. Если удастся заставить конечную точку покинуть сеть и затем присоединиться снова, координатор отправит ей транспортный ключ. Перехват этого пакета может раскрыть сетевой ключ, но сам транспортный ключ зашифрован связным ключом, поэтому нам не обойтись без связного ключа.

Получить сетевой и связной ключи можно различными способами:

  • Проверить известный стандартный связной ключ «ZigBeeAlliance09» (многие устаревшие устройства все еще используют его).
  • Определить производителя устройства и поискать типовые ключи, которые он применяет. Для выяснения производителя можно применить следующие методы:
    • анализ MAC-адреса и/или идентификатора OUI устройства (первые три байта 64‑битного расширенного адреса обычно соответствуют конкретному производителю);
    • физический осмотр устройства (конструкция, маркировка на корпусе и микросхемах).
  • Извлечь прошивки координатора или самого устройства (если есть физический доступ) и попытаться найти встроенные ключи в образах прошивок.

После получения ключей можно легко расшифровать трафик следующим образом:

  1. Открыть дамп трафика в Wireshark.
  2. Перейти в раздел Edit → Preferences → Protocols → Zigbee.
  3. Добавить сетевой ключ и любые имеющиеся связные ключи.
  4. После этого Wireshark будет отображать расшифрованную полезную нагрузку APS и пакеты уровней протокола Zigbee.

После успешной расшифровки мы сможем видеть типы пакетов и читаемые команды прикладного уровня (например, Link Status или команды кластера On/Off):

Выбор подходящего инструмента

Теперь, когда мы можем читать и потенциально расшифровывать трафик, нам нужны аппаратные и программные средства для инъекции пакетов в канал Zigbee между координатором и конечной точкой. В целях исследования мы постарались выбрать недорогие, распространенные и простые в настройке инструменты.

В качестве аппаратного решения мы использовали USB-модуль nRF52840 — тот же, что применялся для сниффинга. Он недорог, его легко найти в продаже, и он поддерживает протоколы IEEE 802.15.4/Zigbee, поэтому способен как перехватывать трафик, так и передавать его.

Этот USB-модуль работает с пользовательской прошивкой, что нам и нужно. Хорошей платформой для прошивки является Zephyr RTOS. Zephyr предоставляет API-интерфейс для работы с радиомодулем стандарта IEEE 802.15.4, который позволяет устройству принимать необработанные фреймы (фактически работать в режиме сниффера), а также отправлять их, как показано в примерах ниже.

Используя этот API и другие компоненты, мы создали реализацию трансивера на C, скомпилировали ее в прошивку и записали на USB-модуль. Прошивка предоставляет простой интерфейс для доступа к среде выполнения, например, через USB‑порт, чтобы мы могли управлять радиомодулем с ноутбука.

Во время работы USB-модуль ожидает передачу данных на последовательном порте (например, /dev/ttyACM1). Мы можем отправлять модулю последовательности байтов с помощью скрипта: прошивка передаст эти данные в API радиомодуля и отправит их в эфир на выбранном канале. Небольшой пример кода на Python, открывающего последовательный порт, выглядит так:

Чтобы формировать Zigbee‑пакеты, мы использовали библиотеку Scapy с расширениями для 802.15.4/Zigbee. Scapy позволяет собирать пакеты по уровням (MAC → NWK → APS → ZCL; об уровнях APS и ZCL мы поговорим чуть позже), а затем преобразовывать их содержимое в последовательности байтов для передачи на USB-модуль.

Ниже приведен пример того, как можно сформировать пакет уровня APS с помощью Scapy:

Перед отправкой пакет должен быть правильно зашифрован и подписан, чтобы конечная точка его приняла. Для этого требуется применить алгоритм AES в режиме CCM (AES-128 с кодом целостности сообщения — MIC), используя сетевой ключ (или корректный связной ключ) в соответствии с правилами Zigbee для шифрования пакетов и вычисления MIC. Мы реализовали шифрование и вычисление MIC на Python (с использованием криптографической библиотеки) после создания пакета в Scapy, а затем отправили получившиеся байты на USB-модуль.

Ниже показан пример кода функций шифрования и вычисления MIC:

Создание пакета

Теперь, когда мы знаем, как внедрять пакеты, нужно понять, какие именно пакеты нужно внедрять. Поскольку координатор передает команды на переключение реле и без нашего вмешательства, нам достаточно отследить такую команду и создать пакет на ее основе. Самый простой способ найти такую команду — перехватить трафик и проанализировать полезную нагрузку прикладного уровня. Однако в перехваченном трафике в Wireshark можно заметить множество ZCL-пакетов, помеченных как [Malformed Packet] — «некорректный пакет».

Наличие «некорректных» ZCL-пакетов обычно означает, что Wireshark не удалось полностью разобрать их содержимое, потому что прикладной уровень нестандартный или содержит меньше данных, чем Wireshark ожидает увидеть. Чтобы понять, почему это происходит, давайте разберемся в устройстве прикладного уровня Zigbee.

Прикладной уровень Zigbee состоит из четырех частей:

  • Application Support Sublayer (APS) направляет сообщения к нужному профилю, конечной точке и кластеру, а также обеспечивает защиту на уровне приложения.
  • Application Framework (AF) содержит прикладные объекты, реализующие функциональность устройства. Эти объекты размещаются на конечных точках (логические адреса 1–240) и предоставляют кластеры (наборы атрибутов и команд).
  • Zigbee Cluster Library (ZCL) определяет стандартные кластеры и команды, обеспечивая взаимную совместимость устройств.
  • Zigbee Device Object (ZDO) отвечает за обнаружение устройств и управление ими (в рамках этого материала не рассматривается).

Чтобы разобраться в трафике прикладного уровня, введем три понятия:

  • Профиль — это набор правил, определяющих поведение устройств в конкретном сценарии использования. Публичные (стандартные) профили поддерживаются разработчиками Zigbee — организацией Connectivity Standards Alliance (CSA). Однако производители могут создавать свои частные профили с проприетарными функциями.
  • Кластер — набор атрибутов и команд, отвечающих за конкретную функцию. Например, кластер On/Off содержит команды On и Off, а также атрибут OnOff, отражающий текущее состояние.
  • Конечная точка — логический «порт» устройства, на котором располагаются профиль и кластеры. Одно устройство может иметь несколько конечных точек для разных функций.

В итоге в типичном трафике Zigbee для домашней автоматизации APS может указывать на профиль Home Automation, кластер On/Off и конечную точку назначения (например, номер 1). В ZCL байт 0x00 часто обозначает команду Off.

Во многих промышленных системах производители используют частные профили или собственные прикладные фреймворки. Из-за этого Wireshark не может корректно декодировать такие пакеты: полезная нагрузка AF нестандартна, и диссектор просто не знает ее формат.

Как же определить нужные байты для переключения реле, если прикладной уровень имеет частную реализацию? Наша стратегия состоит из двух этапов.

  1. Пассивный этап
    Мы перехватываем трафик системы, работающей в штатном режиме. Например, можно активировать реле через другой интерфейс (Ethernet или Bluetooth) и в это время захватить Zigbee-пакеты, которые используются для его переключения. Если удастся расшифровать перехваченные данные, мы сможем извлечь оттуда полезную нагрузку прикладного уровня, соответствующую командам включения и выключения.
  2. Активный этап

    Получив легитимную полезную нагрузку, мы можем перейти к формированию собственного пакета. У нас есть два пути. Во-первых, можно попытаться воспроизвести или продублировать захваченную полезную нагрузку прикладного уровня в неизменном виде. Это сработает, если в системе нет механизмов проверки актуальности, например порядковых номеров. Если же такие проверки есть, потребуется реверс-инжиниринг полезной нагрузки и корректировка всех счетчиков или полей, которые препятствуют принятию нашего дубликата. Например, во многих системах на прикладном уровне имеется счетчик. Если устройство игнорирует пакеты с меньшим значением счетчика, необходимо найти соответствующее поле и увеличить его при формировании собственного пакета.

    Еще один важный механизм защиты — счетчик фреймов в заголовке безопасности Zigbee (в полях безопасности сетевого заголовка). Он предотвращает атаки повторного воспроизведения (replay-атаки): получатель ожидает, что значение счетчика будет увеличиваться с каждым новым пакетом, и отклоняет любые пакеты с меньшим или повторяющимся значением.

Поэтому на активном этапе необходимо:

  1. Перехватывать трафик, пока координатор не отправит конечной точке действительный пакет.
  2. Расшифровать пакет, извлечь счетчики и увеличить их на единицу.
  3. Сформировать пакет с корректно заполненными полями APS/AF (профиль, конечная точка, кластер).
  4. Добавить в пакет корректную ZCL-команду или выявленную на пассивном этапе полезную нагрузку, специфическую для производителя.
  5. Зашифровать и подписать пакет, используя корректный сетевой или связной ключ.
  6. Убедиться, что и счетчик прикладного уровня (если используется), и счетчик фреймов Zigbee были правильно обновлены, чтобы пакет был принят.

Таким образом, стратегия на этом этапе будет выглядеть так:

Если все вышеперечисленное выполнено корректно, мы сможем перехватывать коммуникацию по протоколу Zigbee и переключать реле (включать и выключать его), используя только Zigbee‑соединение.

Подмена координатора (атака с повторным присоединением)

Цель этого вектора атаки — вынудить конечную точку Zigbee покинуть сеть исходного координатора и присоединиться к нашей поддельной сети, чтобы мы смогли взять управлять конечным устройством. Для этого необходимо выполнить две задачи:

  1. Заставить конечную точку выйти из прежней сети.
  2. Подменить координатор и убедить конечную точку присоединиться к нашему поддельному координатору.

Принудительный выход из сети

Чтобы лучше разобраться в манипуляции подключениями конечной точки, сначала опишем концепцию фрейма-маркера (beacon frame). Фреймы-маркеры — это периодические широковещательные сообщения, которые отправляются координатором и маршрутизаторами. Они сообщают о наличии сети и содержат данные, необходимые для присоединения, такие как:

  • PAN ID и Extended PAN ID;
  • адрес координатора;
  • информация о стеке/профиле;
  • сведения о доступности ресурсов (например, может ли координатор принимать дочерние устройства).

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

Важно отметить, что фреймы-маркеры существуют как на уровне Zigbee, так и на уровне IEEE 802.15.4: уровень MAC задает базовую структуру маркера, а Zigbee дополняет ее сетевыми полями.

Мы можем заставить конечную точку покинуть свою сеть, воспользовавшись тем, как Zigbee обрабатывает конфликтующие PAN. Если координатор обнаруживает маркеры другого координатора, работающего с тем же PAN ID и на том же канале, он может инициировать процедуру разрешения конфликтующих PAN. В ходе этой процедуры координатор может скомандовать своим узлам изменить PAN ID и присоединиться заново, что приводит к их выходу из сети и попытке повторного присоединения. Это окно повторного присоединения дает нам возможность внедрить собственный поддельный координатор и перехватить подключающееся устройство.

В захваченном трафике ниже пакет 7 — это маркер, сгенерированный нашим поддельным координатором, использующим тот же PAN ID, что и реальная сеть. В результате конечная точка с адресом 0xe8fa выходит из сети (см. пакеты 14–16).

«Выбери меня»

После того как мы отправили поддельный маркер и заставили конечную точку покинуть исходную сеть, следующий шаг — убедить эту конечную точку выбрать наш поддельный координатор. На этом этапе предполагается, что у нас уже есть необходимые ключи (сетевой и связной) и понимание логики работы конечной точки, с которой мы взаимодействуем.

Чтобы успешно выдавать себя за настоящий координатор, поддельный узел должен отвечать на каждый запрос-маркер, который отправляет конечная точка. Ответный маркер должен содержать тот же Extended PAN ID и другие ожидаемые конечной точкой поля. Если конечная точка сочтет наш маркер корректным, она попытается присоединиться к нашему устройству.

Разумными представляются два способа заставить конечную точку предпочесть наш координатор:

  1. Заглушить сигнал настоящего координатора
    Нужно ослабить сигнал реального координатора на стороне конечной точки, чтобы он выглядел слабее и вынуждал конечную точку выбрать наш маркер. Для этого потребуется дополнительное оборудование.
  2. Эксплуатировать специфичное для производителя или неописанное поведение
    Zigbee-стеки иногда ведут себя по-разному в зависимости от производителя. Например, в маркере есть одно полезное поле — Update ID. Оно увеличивается, когда координатор изменяет конфигурацию сети.

Если два координатора объявляют один и тот же Extended PAN ID, но у одного значение Update ID выше, некоторые стеки будут предпочитать маркер с более высоким Update ID. Такое поведение непостоянно, зависит от реализации стека и может работать не везде. Иногда нам удавалось добиться нужного эффекта, а иногда — нет. Существует множество других подобных особенностей, которые стоит перепробовать при оценке защищенности.

Даже если конечная точка выберет наш поддельный координатор, соединение может оказаться нестабильным. Основная проблема заключается в таймингах: конечная точка ожидает подтверждений (ACK) для фреймов, которые она отправляет координатору, а также быстрых ответов на пакеты, связанные с установлением соединения. Если наш имитатор реализован на Python на ноутбуке, который принимает пакеты, формирует ответы и затем передает их через USB-модуль, суммарная задержка окажется слишком большой. В результате конечная точка не получит своевременные ACK и ответные пакеты и разорвет соединение.

Таким образом, нам нужно не просто подделать несколько пакетов, а фактически воссоздать отдельные части стеков Zigbee и IEEE 802.15.4, которые должны работать быстро и надежно. Моделирование такого поведения на высокоуровневом интерпретируемом языке обычно слишком медленное для реальных стеков.

Практическое решение — запустить полноценный Zigbee‑стек координатора непосредственно на USB-модуле. Например, USB-модуль nRF52840 может работать как координатор, если записать на него соответствующую прошивку из Nordic SDK (см. образец координатора сети на сайте Nordic). Так мы получаем минимальную задержку и отправку ACK-пакетов, необходимые для стабильного соединения.

Однако у такого простого решения есть серьезный недостаток. При работе с промышленными реализациями систем на основе Zigbee часто возникают проблемы с совместимостью. В своих тестах мы сравнивали фреймы-маркеры от реального координатора и от прошивки координатора Nordic. Важные для нас различия были в заголовках профиля стека:

Строка Stack Profile определяет тип сетевого профиля. Распространенные значения включают: 0x00 — Network Specific (частный) профиль, 0x02 — Zigbee PRO (публичный) профиль.

Если конечная точка ожидает конкретный сетевой профиль (то есть частный профиль производителя), а мы предоставляем Zigbee PRO, конечная точка откажется присоединяться. Устройства, поддерживающие только частные профили, не присоединятся к сетям с публичными профилями, и наоборот. В нашем случае мы не смогли изменить прошивку Nordic так, чтобы она соответствовала проприетарному профилю стека, поэтому конечная точка отказалась присоединяться.

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

Возврат к истокам

В предыдущем подходе мы использовали сниффер в неизбирательном режиме, который получает каждый фрейм в радиоэфире независимо от его пункта назначения. Реальные узлы на базе Zigbee (IEEE 802.15.4) работают иначе. На уровне MAC/802.15.4 узел фильтрует фреймы по PAN ID и адресу назначения. Фрейм передается на верхние уровни, только если PAN ID совпадает, а адрес назначения соответствует адресу узла или является широковещательным.

Мы можем воспроизвести такое поведение на USB-модуле, запустив на нем Zephyr RTOS и настроив модуль как простейший координатор 802.15.4. Для этого мы задаем PAN ID и короткий сетевой адрес на модуле, чтобы нужный узел принимал только фреймы, соответствующие этим критериям. Это важно, так как позволяет модулю автоматически обрабатывать ACK и временные интервалы на уровне MAC: модуль будет немедленно отправлять ACK на уровне MAC.

Настроив USB-модуль для выполнения задач на уровне MAC (фильтрации по PAN и отправки ACK), мы можем реализовать логику Zigbee на Python. Инструмент Scapy упрощает создание пакетов: с его помощью мы можем формировать собственные маркеры с заголовками, соответствующими заголовкам оригинального координатора, что решает проблему несовместимости. Однако нам по-прежнему предстоит написать код конечного узла для Zigbee: инициацию соединения, подключение, обработку сетевого ключа, работу уровней APS/AF, обработку прикладной полезной нагрузки и так далее. Это самая сложная часть.

Существует одна проблема с таймингами, которую невозможно решить на Python: при открытии соединения требуется мгновенная отправка пакетов. Для решения этой задачи критические по времени фрагменты мы написали на языке C и внедрили в прошивку USB-модуля. Например, пакеты для инициации соединения можно заранее сгенерировать с помощью Python и встроить в прошивку. Затем с помощью if-инструкций мы можем определить, как реагировать на каждый пакет от конечной точки.

Таким образом, прошивка USB-модуля (C/Zephyr) обрабатывает ACK на уровне MAC и начальное рукопожатие при подключении, а при помощи Python мы формируем пакеты более высокого уровня и указываем, что модуль должен отправлять в дальнейшем при обработке трафика прикладного уровня. Такая гибридная модель уменьшает задержки и обеспечивает стабильное соединение. Окончательная архитектура выглядит следующим образом:

Получение ключа

Кратко напомним, как работает процесс присоединения: конечная точка Zigbee транслирует запросы-маркеры по каналам, ожидает ответов-маркеров, выбирает координатор, отправляет запрос на подключение и затем запрашивает данные для получения короткого адреса. Затем координатор отправляет пакет транспортного ключа, содержащий сетевой ключ. Если конечная точка имеет правильный связной ключ, она может расшифровать пакет транспортного ключа и получить сетевой ключ, то есть аутентифицироваться. С этого момента сетевой трафик шифруется с использованием сетевого ключа. Весь процесс выглядит так:

Сложность заключается в пакете транспортного ключа. Этот пакет защищен с помощью связного ключа — уникального ключа для каждого устройства, известного координатору (центру доверия) и присоединяющейся конечной точке. Прежде чем использовать связной ключ для шифрования, часто требуется обработать его (хэшировать или получить производный ключ) в соответствии с правилами формирования ключей в Zigbee. Простой реализации этого алгоритма хэширования на Python не существует, поэтому нам пришлось написать ее самостоятельно.

Мы разработали процедуру формирования необходимого ключа; ее код доступен на нашем GitHub.

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

Окончательный успех

Выполнив все описанные выше шаги, мы можем заставить конечную точку присоединиться к нашему поддельному координатору. Обычно после присоединения она остается связанной с ним, даже если мы выключим координатор (пока какое‑то событие снова не заставит ее проанализировать соединение). С этого момента мы можем взаимодействовать с устройством на прикладном уровне, используя Python. Получив доступ через координатор, мы смогли включать и выключать реле, как и планировалось, а также получили значительно более широкие возможности благодаря расширенному контролю над узлом.

Заключение

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

Как мы увидели, неправильная конфигурация Zigbee может стать причиной полной компрометации сети. Для повышения безопасности Zigbee всегда используйте функции защиты из последних спецификаций, например генерацию уникальных связных ключей для каждого устройства с помощью кодов установки. Также следует избегать использования встроенных или стандартных ключей. Кроме того, мы рекомендуем не применять модель шифрования при помощи сетевого ключа и настраивать сквозное шифрование на прикладном уровне в качестве дополнительной меры защиты к используемым на сетевом уровне.

Перехватываем «рубильник»: оценка безопасности протокола Zigbee в промышленной среде

Ваш e-mail не будет опубликован. Обязательные поля помечены *

 

Отчеты

ToddyCat — ваш скрытый почтовый ассистент. Часть 1

Эксперты «Лаборатории Касперского» разбирают атаки APT ToddyCat через корпоративную электронную почту. Изучаем новую версию TomBerBil, инструменты TCSectorCopy и XstReader, а также способы кражи токенов доступа из Outlook.

Криптоафера группы BlueNoroff: «призрачные» инвестиции и фиктивные рабочие предложения

Эксперты команды GReAT проанализировали кампании GhostCall и GhostHire APT-группы BlueNoroff: несколько цепочек вредоносного ПО для macOS, поддельные клиенты Zoom и Microsoft Teams, а также изображения, улучшенные с помощью ChatGPT.

Mem3nt0 mori – Hacking Team снова с нами!

Исследователи «Лаборатории Касперского» впервые обнаружили шпионское ПО Dante, разработанное Memento Labs (бывшей Hacking Team) в дикой природе и нашли его связь с APT ForumTroll.