Статья Чтение тегов контроллеров через недокументированные функции

1771958148568.webp


Сегодня о том, как заглянуть в кишки программируемому логическому контроллеру (PLC) без карты местности, то есть без исходного кода, без символов, без подсказок от вендора - голыми руками, через недокументированные функции инженерного софта и протоколов.

Зачем это всё? От благородных целей до чёрного рынка​

Причины, по которым тебе может понадобиться прямой доступ к памяти контроллера, могут быть совершенно разными. И я не буду делить их на «белые» и «чёрные», потому что в реальном мире всё смешано.

Легитимные сценарии (для тех, кто платит налоги и спит спокойно):
  1. Восстановление утерянного проекта. Классика жанра. Жёсткий диск сгорел, бэкапов нет, подрядчик обанкротился и пропал, а станок стоит и требует модернизации. Единственный источник правды - сам контроллер. Но из него нельзя выгрузить исходный код в виде лестничных диаграмм или структурированного текста - только бинарные блоки. Зная, как читать память напрямую, можно вытащить хотя бы данные (уставки, текущие значения) и, сопоставив с аппаратной обвязкой, восстановить логику.
  2. Аудит безопасности. Ты - ответственный инженер или пентестер, которого наняли проверить завод на прочность. Ты должен знать, может ли злоумышленник, проникший в сеть, остановить конвейер, изменить рецептуру или открыть клапан с кислотой. Самый верный способ - попробовать сделать это самому, используя те же инструменты, что и хакер. Если ты сможешь прочитать память без авторизации - значит, и злоумышленник сможет. Если сможешь записать - пиши акт.
  3. Диагностика неочевидных глюков. Контроллер работает, но периодически выдает ошибки, которые не ловятся стандартными средствами. Нет проекта, нет документации. Ты можешь подключиться, наблюдать за изменениями в памяти в реальном времени, искать залипшие биты, аномальные скачки значений, корреляции между событиями - и найти причину, которую вендор искал бы месяцами.
  4. Исследование и образование. Ты хочешь понять, как на самом деле работают проприетарные протоколы, как контроллеры общаются с инженерным софтом, какие есть закладки и уязвимости. Это чистое познание, но оно ведёт к могуществу.
Сценарии "серая зона" (и даже чёрная):
  1. Промышленный шпионаж. Конкурент хочет узнать, как сделана уникальная установка, чтобы скопировать или найти слабые места. Слить проект через флешку сложно, а вот подключиться к контроллеру по сети и вычитать память - вполне реально.
  2. Саботаж. Недовольный сотрудник или хактивист хочет остановить производство, испортить продукт. Можно не разбираться в логике, а просто найти область памяти, где хранятся критичные параметры, и начать писать туда случайные значения или обнулять их.
  3. Киберпреступность. Вымогатели шифруют файлы на офисных серверах - это скучно. А если они остановят доменную печь или нефтеперекачивающую станцию? Тогда завод заплатит миллионы, лишь бы снова запуститься. И для этого не нужно взламывать сложные SCADA-системы - достаточно уметь писать в память контроллера.

Почему PLC такие уязвимые? Исторический экскурс​

Чтобы понять, почему мы вообще можем читать память контроллера без пароля, нужно нырнуть в историю промышленной автоматизации. 80-е, 90-е годы. PLC разрабатывались как надёжные заменители релейных щитов. Они должны были работать в жёстких условиях, не виснуть годами и быстро выполняться. Протоколы, по которым они общались (Profibus, Modbus, S7, CANopen), создавались для скорости и детерминизма, а не для безопасности. Сеть была изолирована физически (air gap) - к контроллеру мог подключиться только инженер с программатором, воткнув кабель напрямую.

Никто не думал, что эти протоколы когда-нибудь полетят по Ethernet в общую сеть завода, а потом - в интернет. Но прогресс не остановить: появился Industrial Ethernet, Profinet, Ethernet/IP. Контроллеры обзавелись сетевыми картами, и теперь они торчат в локальной сети как новогодние ёлки. А протоколы остались теми же - доверчивыми, как щенки.

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

Поехали, брат. Открывай терминал, запускай Wireshark, готовь кабель. Мы идём в гости к PLC, и хозяин даже не узнает, что мы там были.

Часть 1. Анатомия ПЛК: Вскрытие покажет, где хранятся ваши тайны​

1.1. ПЛК - это просто компьютер, который носит спецовку​

Давай сразу договоримся: ПЛК (программируемый логический контрлер) - это не магическая коробочка с молниями. Это специализированный компьютер. У него есть:
  • Процессор (CPU): Часто это либо сильно урезанный x86 (особенно в старых моделях), либо ARM, либо специальные микроконтроллеры (Infineon TriCore, Renesas, и т.д.). Тактовая частота смешная по меркам твоего ноутбука - сотни мегагерц, редко гигагерцы. Но для реального времени этого хватает.
  • Оперативная память (RAM): Здесь хранятся текущие значения переменных, состояния таймеров, счётчиков, входов/выходов. Обычно объём от нескольких десятков килобайт до нескольких мегабайт. Вся информация в RAM летучая - при выключении питания пропадает, если нет батарейки.
  • Энергонезависимая память (Flash / EEPROM): Здесь лежит сам проект - скомпилированный код программы, начальные значения данных, конфигурация оборудования. При включении контроллер копирует часть данных в RAM и начинает выполнение.
  • Операционная система реального времени (RTOS): Управляет задачами, следит за таймерами, обрабатывает прерывания. Часто это проприетарная разработка вендора, но бывают и коммерческие ОС (VxWorks, QNX, даже Embedded Linux на некоторых мощных контроллерах).
  • Сетевые интерфейсы: Ethernet, RS-485/232, Profibus, CAN и т.д.
Так вот, для нас самое интересное - это то, как организована память и как к ней можно достучаться извне.

1.2. Карта памяти: Где лежит что​

Представь себе память контроллера как огромный склад с ячейками. Вендор (Siemens, Schneider и др.) заранее разбил этот склад на зоны. Каждая зона имеет своё назначение. В документации к контроллеру обычно есть карта памяти, но в общем виде это выглядит так:

Области памяти (на примере Siemens S7, но аналоги есть у всех)​

  • Процессорный образ входа (Process Input Image - PII, часто обозначается I или E): Это зеркало физических входов. Каждый бит в этой области соответствует состоянию конкретного дискретного входа (например, кнопка пуска, концевик). Когда контроллер выполняет цикл, он сначала считывает состояние всех физических входов и копирует их в эту область. Программа уже работает не с реальными входами, а с этой копией. Размер области фиксирован и зависит от модели CPU.
  • Процессорный образ выхода (Process Output Image - PIQ, часто Q или A): Аналогично, но для выходов. Программа пишет значения в эту область, а в конце цикла контроллер разом выставляет все физические выходы в соответствии с этой копией.
  • Меркеры (Merker - M): Внутренняя память для флагов, промежуточных переменных. Просто биты, байты, слова, двойные слова, которые программа может использовать как угодно. Очень удобная область для поиска, потому что она часто не привязана к физическим входам/выходам.
  • Таймеры (Timer - T) и счётчики (Counter - C): Специальные области, где хранятся значения таймеров и счётчиков. Доступ к ним обычно идёт через специальные команды, но в памяти они тоже лежат.
  • Блоки данных (Data Blocks - DB): Это самое мясо. DB - это структурированные области данных, которые создаёт программист. В DB могут лежать любые переменные: от битовых флагов до массивов и структур. Каждый DB имеет свой номер (DB1, DB2, …) и внутреннюю структуру. Для инженерного софта структура известна (она хранится в проекте), для нас - неизвестна, если мы не загрузили проект. Но физически это просто кусок памяти.
  • Системные данные (System Data): Там хранятся диагностические буферы, информация о конфигурации, настройки стека и т.д. Доступ к ним тоже возможен, но сложнее.
У Schneider Electric в Unity Pro (для Modicon M340/M580) используется похожая, но слегка иная адресация: %I, %Q, %M, %MW (слова памяти), %KW (константы), %ID, %QD и т.д. Но суть та же - линейное адресное пространство.

У Rockwell (ControlLogix) - тэговая архитектура. Там нет фиксированных областей I, Q, M, а есть просто тэги (переменные) с именами, которые могут быть как базовыми типами (BOOL, DINT, REAL), так и структурами. Но физически они тоже лежат в памяти по определённым адресам, просто адресация динамическая.

1.3. Теги, переменные и абсолютные адреса​

Теперь важный момент: что мы читаем, когда подключаемся к контроллеру через инженерный софт? Мы видим теги (или переменные). Тег - это имя, которое программист дал какому-то адресу. Например, Engine_Start может соответствовать биту Q0.0. Или Pressure_Tank может соответствовать REAL-переменной в DB1.DBD12.

Инженерный софт (TIA Portal, Unity Pro) хранит в проекте таблицу символов - соответствие имён адресам. Когда вы открываете проект, вы видите удобные имена, и можете следить за их значениями.

Но контроллеру плевать на имена. Контроллер оперирует адресами. Когда программа компилируется, все имена превращаются в конкретные адреса памяти. И когда вы просите контроллер дать значение тега Engine_Start, инженерный софт переводит это в запрос: "Прочитай мне бит по адресу Q0.0". Контроллер отвечает: "Вот бит".

А что, если у нас нет проекта и мы не знаем, какой адрес соответствует какому тегу? Мы всё равно можем читать адреса напрямую. Мы можем сказать контроллеру: "Дай мне 100 байт, начиная с адреса Q0.0". Контроллер даст. Он не спросит: "А зачем тебе? А кто ты?" Он просто отдаст данные. Потому что это его работа - обслуживать запросы от мастер-устройств.

1.4. Инженерный софт как привилегированный клиент​

Инженерный софт - это не просто программа, которая показывает красивые лесенки. Это клиент, который общается с контроллером по определённому протоколу. В этом протоколе заложены различные функции (сервисы):
  • Чтение блока данных: прочитать определённое количество байт из указанного DB (для Siemens) или из указанной области памяти (для других).
  • Запись блока данных: записать байты в память.
  • Чтение/запись отдельных переменных (битов, слов) - как частный случай предыдущего.
  • Управление режимом CPU: перевести в STOP, в RUN, перезагрузить.
  • Загрузка проекта: передать новый код и данные в контроллер.
  • Чтение диагностики: прочитать буфер ошибок.
  • Установка/снятие пароля (если есть).
Эти функции реализованы в прошивке контроллера. Они необходимы для нормальной эксплуатации. Без них инженер не смог бы залить программу или даже просто посмотреть текущие значения.

Ирония в том, что эти функции всегда доступны, если у вас есть сетевое соединение. Да, производители добавляют пароли, но часто они защищают только определённые действия (например, загрузку проекта), а чтение памяти остаётся открытым. Потому что иначе как бы вы мониторили значения в онлайн-режиме без пароля? Вендоры пытаются балансировать между удобством и безопасностью, и, как обычно, удобство побеждает.

1.5. Что такое «недокументированные функции»?​

Термин звучит зловеще, как что-то из арсенала спецслужб. На самом деле, всё проще. Есть официальная документация по протоколам, которую вендоры выкладывают в открытый доступ. Там описаны основные функции, которые нужны для разработки приложений верхнего уровня (SCADA, OPC-серверы).

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

Недокументированные функции - это просто коды операций (function codes), которые прошивка контроллера распознаёт, но которые не описаны в публичных спецификациях. Их находят исследователи методом реверс-инжиниринга: сниффают трафик между инженерным софтом и контроллером, видят странные запросы, анализируют, пробуют повторить. Так рождаются библиотеки вроде Snap7, которые умеют делать с контроллером всё то же, что и родной Step7, но без лицензии и без графического интерфейса.

1.6. Чтение тегов через чтение памяти​

Вернёмся к нашим баранам. Нам нужно читать теги (переменные), не зная логики программы. Как это сделать?

Метод 1: Чтение по абсолютным адресам. Если мы знаем, что в данном контроллере входы лежат по адресам I0.0 – I0.7, выходы по Q0.0 – Q0.7, а данные, скорее всего, в DB, мы можем начать читать их все подряд и анализировать.

Метод 2: Поиск по шаблонам. Если мы знаем тип переменной (например, REAL - 4 байта в формате IEEE754), мы можем сканировать память в поисках значений, которые выглядят как правдоподобные числа (не слишком большие, не слишком маленькие, меняются плавно). Это поможет найти аналоговые сигналы.

Метод 3: Динамический анализ. Мы воздействуем на процесс (нажимаем кнопку, меняем уставку через HMI) и смотрим, какие байты в памяти изменились. Те, которые изменились - вероятно, связаны с нашим воздействием. Повторяем несколько раз, сужаем круг.

Метод 4: Использование уязвимостей. Иногда можно обойти защиту и выгрузить таблицу символов прямо из контроллера (например, в старых версиях Siemens S7-1200 символическая информация загружалась в контроллер и могла быть считана). Но в новых версиях это обычно отключено.

1.7. Примеры конкретных протоколов и их возможностей​

Siemens S7 (S7-300, 400, 1200, 1500)​

  • Протокол: S7 communication (ISO-on-TCP, порт 102).
  • Основные функции: чтение/запись DB, меркеров, входов/выходов, таймеров, счётчиков.
  • Недокументированные: функции управления CPU (запуск/останов), чтение/запись системных данных, загрузка блоков, управление паролями.
  • Инструменты: Snap7, python-snap7, libnodave (старьё), MHJ-S7 (на C#).

Schneider Electric (Modicon M340/M580)​

  • Протокол: Modbus TCP (порт 502) и UMAS (проприетарный, порт 502 или другой).
  • Modbus TCP: позволяет читать и писать регистры %MW, биты %M, входы %I, выходы %Q. Это документировано, но часто не защищено. По умолчанию доступны все регистры.
  • UMAS: используется для загрузки проекта, управления CPU, чтения диагностики. Реверсирован, есть библиотеки (например, на Python - umasci).
  • Инструменты: pymodbus, modpoll, umasci.

Rockwell Automation (ControlLogix, CompactLogix)​

  • Протокол: CIP (Common Industrial Protocol) поверх Ethernet/IP (порт 44818).
  • Чтение/запись тэгов осуществляется через CIP-сервисы. Тэги имеют имена, но можно читать данные по адресам, если знать структуру памяти. Исследователи нашли недокументированные функции для управления CPU и загрузки проектов.
  • Инструменты: libplctag, pycomm3, CPPPO.

1.8. Модель угроз: Кто и зачем?​

Давай теперь приземлимся. Кто может реально использовать эти методы?
  • Инсайдер (обиженный сотрудник): Имеет физический доступ к сети, знает топологию, может подключить ноутбук и начать сканировать. Его цель - саботаж или кража интеллектуальной собственности.
  • Внешний злоумышленник: Проник в офисную сеть через фишинг, затем через плохо сегментированную сеть добрался до промышленного сегмента. Использует сканеры для поиска PLC, затем эксплуатирует их доверчивость.
  • Пентестер (мы с тобой в белой шляпе): Нанимается для проверки безопасности. Должен показать, что может прочитать память и изменить значения, чтобы заказчик понял риски.
  • Исследователь: Просто хочет понять, как работает оборудование, и делится знаниями с сообществом.

1.9. Почему это работает?​

Всё упирается в фундаментальное свойство: PLC не аутентифицирует клиента на уровне протокола. Есть исключения (например, Siemens S7-1500 с включённой защитой может требовать пароль даже на чтение), но по умолчанию - открыто. Протоколы разрабатывались для замкнутых сред, где все устройства доверяют друг другу. Перенос их в IP-сети без дополнительной защиты - как оставить ключи в замке зажигания и уйти.

Производители сейчас пытаются исправиться: в новых версиях появляются функции: разрешить чтение только с определённых IP-адресов, требовать пароль на запись, шифрование. Но старых контроллеров на заводах - миллионы, и менять их никто не будет. Так что на ближайшие 20 лет это всё актуально.

Часть 2. Анатомия: Как программа лежит в памяти контроллера​

Структура памяти (сильно упрощенно, но для понимания сути - ок):
  1. Область кода (Code Block / Organization Blocks - OB, Function Blocks - FB, Functions - FC): Это та самая логика. Если мы её не знаем, мы не можем её прочитать в виде исходников напрямую из контроллера (если только вендор не оставил там отладочные символы, но об этом позже). Мы можем сдампить её как бинарный файл, но декомпиляция в читаемый ST или лестницу - это отдельный уровень джедайства.
  2. Область данных (Data Blocks - DB): Вот наше всё. DB - это структурированная область памяти, где хранятся переменные программы: текущие значения датчиков, уставки, таймеры, счётчики. Инженерный софт, когда вы открываете проект и подключаетесь к контроллеру, умеет считывать эти DB и показывать их в удобном виде, потому что у него есть карта переменных (символы).
  3. Системная память (Merker - M, Inputs - I, Outputs - Q, Timers - T, Counters - C): Это стандартные области. Адресация прямая. В Siemens это M0.0, I0.0, Q0.0 и так далее.
Вот тут и кроется наша дыра. Если у нас нет символов (имен переменных типа Pressure_Valve_5), то мы видим просто сырую память. Но кто сказал, что нам нужны имена? Нам нужно управлять процессом.

Если мы знаем, что давление в ёмкости регулируется заслонкой, которая висит на выходе Q0.7, нам плевать, как технолог назвал эту переменную. Мы просто пишем 1 в бит Q0.7 и клапан открывается. Вопрос в том, как найти, какой бит за что отвечает? И тут начинается самое интересное: анализ поведения.

Часть 3. Инструментарий: Строим мост к контроллеру​

Хватит теории. Давай говорить о конкретике. Мы не будем пользоваться только мышем и кликать в графическом интерфейсе (хотя и это метод). Мы будем использовать инструменты, которые дают нам прямой, нефильтрованный доступ к протоколу.

Инструмент 1: Snap7 (и его обертки)​

Это, пожалуй, легенда. Snap7 - это открытая библиотека на C++, которая полностью эмулирует поведение клиента S7 (для Siemens S7-300, S7-400, S7-1200, S7-1500). Она обращается к контроллеру по протоколу S7, не требуя наличия Step7 или TIA Portal.

Что она нам дает?
  • Чтение и запись любой области памяти (DB, M, I, Q, C, T).
  • Чтение и запись любых блоков данных.
  • Управление состоянием контроллера (RUN/STOP).
  • И всё это - через Python (есть обертка python-snap7).
Пример кода на Python, который читает первый Data Block (DB1) целиком:

Python:
import snap7

# Подключаемся к контроллеру по IP
client = snap7.client.Client()
client.connect('192.168.0.1', 0, 1) # IP, rack, slot (обычно 0,1 для S300)
if client.get_connected():
    print("Связь есть, брат!")

    # Читаем DB номер 1, начиная со смещения 0, длиной 100 байт
    data = client.db_read(1, 0, 100)
    print(data) # Увидим массив байт.

    # Записываем что-то в DB1 по смещению 10 (например, байт 0xFF)
    client.db_write(1, 10, b'\xFF')
    print("Записали байт по адресу DB1.DBB10")

    # Меняем режим контроллера
    client.plc_stop()
client.plc_start()

Видишь? Никаких паролей. Если контроллер в сети и не имеет настроек безопасности на уровне доступа (а по умолчанию их часто нет), мы - короли.

Инструмент 2: libplctag (для ControlLogix/CompactLogix от Rockwell)​

Если ты имеешь дело с продукцией Rockwell Automation (Allen-Bradley), там своя песня - протокол CIP. Но для него есть libplctag. Это библиотека, которая позволяет читать и писать тэги. Принцип тот же: подключаемся, читаем память.

Инструмент 3: pyads (для TwinCAT от Beckhoff)​

TwinCAT от Beckhoff использует протокол AMS (производный от того, что использовался в старом добром S5). Библиотека pyads дает прямой доступ к переменным по имени. Если имена неизвестны, можно сканировать память.

Инструмент 4: Инженерный софт (в «нелегитимном» режиме)​

Да, сам софт тоже подходит. Но мы будем использовать его хитро. Забудем про открытие проекта. Мы просто создадим пустой проект (или любой левый), сконфигурируем железо (CPU) и подключимся к контроллеру. В Siemens TIA Portal есть функция "Online & Diagnostics" и "Go online" (работа в режиме онлайн без загрузки проекта). Там можно смотреть таблицы переменных (Watches/Forces) и смотреть сырые значения.

Но есть нюанс: TIA Portal захочет, чтобы структура данных в проекте совпадала с контроллером для удобного отображения. Он может отказаться показывать DB, если у него нет информации о типах. Но! Он всегда может показать системные области (M, I, Q). Это уже хлеб.

Часть 4. Практика: Методология разведки (Recon)​

Допустим, мы подключились к контроллеру неизвестной установки. Мы не знаем логики, не знаем, где что лежит. Наша задача - найти критические переменные.

Шаг 1. Сканирование памяти.

Мы не будем тупо качать все DB подряд. Мы будем искать паттерны.
Используем скрипт на Python (через snap7), который будет пробегаться по адресам и искать определенные значения.
  • Ищем константы (часто встречаются числа, похожие на уставки: 100, 50, 1013 (гПа), 0x4000 (16mA) и т.д.).
  • Ищем граничные значения. Если мы нашли байт, который постоянно меняется, возможно это статус.
  • Ищем счетчики. Если значение увеличивается на 1 каждую секунду, это скорее всего счетчик часов работы.
Шаг 2. Анализ поведения (динамическое воздействие).

Вот тут начинается магия. Мы будем дергать за переменные и смотреть, что изменится в реальном мире (или в показаниях других переменных). Техника называется "Fuzzing" применительно к полям ввода-вывода, но с умным подходом.
  1. Читаем дамп всей памяти (например, DB1 длиной 500 байт) и сохраняем его.
  2. Воздействуем на процесс. Идем и жмем кнопку на реальном пульте. Или, если есть такая возможность, меняем какой-то известный нам параметр (например, открываем кран вручную). В этот момент читаем дамп памяти снова.
  3. Сравниваем дампы (diff). Байты, которые изменились - это наши кандидаты на роль входов (I) или битов статуса. Если мы нажали кнопку "Пуск" на щите, мы увидим изменение конкретного бита во входном образе (I-области) или в DB.
  4. Запись и наблюдение. Теперь мы хотим найти выходы (Q). Мы пишем 1 в какой-нибудь подозрительный бит в области Q или DB, и слушаем, не щелкнет ли где реле, не зажжется ли лампочка. Только осторожно! Можно что-нибудь сломать, если это управление двигателем. Лучше начинать с маломощных индикаторов.

Часть 5. Разбор по вендорам: Особенности национальной охоты​

Siemens (Step7 / TIA Portal)​

  • Святая святых: Data Blocks (DB).
  • Протокол: S7 Communication (ISO-on-TCP, порт 102).
  • Недокументированная фича: Функция чтения/записи любого DB без знания его структуры (это как раз реализовано в Snap7). Контроллеру плевать, что ты просишь 20 байт из середины DB, если эти байты существуют. Он их просто отдаст.
  • Практический прием с TIA Portal:
    1. Создаем новый проект, добавляем CPU того же типа, что и целевой.
    2. В свойствах CPU на вкладке "Protection" выставляем самый низкий уровень (если он другой, может не дать подключиться).
    3. Идем в "Go online". Выбираем интерфейс.
    4. TIA Portal обнаружит расхождение между онлайн-устройством и офлайн-проектом.
    5. Выбираем "Upload from device" (загрузить из устройства). Это загрузит аппаратную конфигурацию и блоки в твой проект. ВНИМАНИЕ: это загрузит код (FC, FB) в виде байт-кода, но не исходный код. Данные (DB) загрузятся со значениями по умолчанию? Нет, они загрузятся с текущими значениями.
    6. После загрузки ты можешь открыть DB и увидеть их структуру и текущие значения. Ты не увидишь исходников логики, но ты увидишь названия переменных (если они не были стерты при компиляции!).
  • Важно: В новых версиях TIA Portal есть механизм защиты, который может блокировать чтение блоков (Know-How Protection). Но она часто защищает только код, а данные (DB) остаются доступными. Да и сам Know-How Protection ломается тривиальным изменением одного байта в заголовке блока.

Schneider Electric (Unity Pro / EcoStruxure Control Expert)​

  • Святая святых: Секции данных и функциональные блоки (DFB). Память организована немного иначе, но суть та же. Есть адресное пространство %M (биты), %MW (слова), %I, %Q.
  • Протокол: Modbus TCP (по умолчанию порт 502) и UMAS (проприетарный поверх TCP).
  • Недокументированная фича: Функции UMAS, которые позволяют читать/писать память, останавливать/запускать ПЛК, загружать проекты. Modbus - это вообще открытая книга.
  • Практический прием:
    1. Используем обычный Modbus клиент (например, утилиту modpoll или библиотеку pymodbus).
    2. Читаем регистры %MW. Если у нас нет адресов, мы можем просто начать читать регистры подряд.
      • %MW100: значение 0
      • %MW101: значение 16000 (может быть, аналоговый вход?)
      • %MW102: значение 0
    3. Если контроллер настроен на работу с Modbus, то по умолчанию доступ к регистрам может быть не ограничен. И мы можем писать в них. Это как раз прямой доступ к оперативной памяти контроллера.
    4. Для UMAS все сложнее. Можно использовать утилиты вроде plcscan или писать свой скрипт, который будет дергать функции. Но для новичка хватит и Modbus.

Часть 6. Техника "Черного ящика": Поиск уязвимостей в логике​

Допустим, мы можем читать и писать память. Но как понять, что мы не сломаем станок? Для этого нужно понимать логику "снаружи". Представь, что логика - это функция F(Inputs) -> Outputs. Мы можем подавать на вход произвольные значения и смотреть на выход. Это называется моделированием.

Пример:
Есть программа, управляющая насосом. Насос включается, если уровень в резервуаре ниже 2 метров и если не нажата кнопка аварийного останова.
Нам не обязательно читать код программы, чтобы это понять. Мы можем:
  1. Найти переменную уровня (путем анализа дампов, как описано выше). Пусть это будет DB1.DBD12 (real).
  2. Найти выход управления насосом. Пусть это будет Q0.0.
  3. Найти вход кнопки аварийного останова. Пусть это будет I0.1 (инверсная логика, обычно).
    Теперь, меняя значение DB1.DBD12 в сторону уменьшения и смотря на Q0.0, мы увидим порог срабатывания. Мы выяснили алгоритм, не имея исходников.

Часть 7. Инструментарий для глубокого анализа (для самых упертых)​

7.1. Зачем вообще нужно глубоко копать?​

Скажи мне, зачем тебе Wireshark, если есть Snap7, который и так умеет читать DB? Ответ прост: Snap7 реализует известные функции протокола. А что, если контроллер новой модели, и старые функции там не работают? Или если защита включена, но мы подозреваем, что есть обходной путь? Или мы хотим найти те самые недокументированные функции, которые инженерный софт вызывает, а в документации их нет?

Без глубокого анализа ты будешь слепым котёнком. Ты не узнаешь, какие ещё сервисы предоставляет контроллер, какие уязвимости в прошивке можно дёрнуть. Поэтому мы начинаем с самого базового - с прослушки трафика.

7.2. Wireshark: Глаза и уши хакера​

Wireshark - это, без преувеличения, швейцарский нож сетевого аналитика. Если ты его ещё не установил - бегом ставить. Это бесплатно, кроссплатформенно, и умеет разбирать сотни протоколов, включая промышленные (S7, Modbus/TCP, Profinet, Ethernet/IP, CIP и многие другие).

7.2.1. Первый запуск и захват трафика​

Допустим, у тебя есть инженерная станция с TIA Portal и контроллер Siemens S7-1200. Ты хочешь понять, что именно происходит, когда ты нажимаешь «Go online» и смотришь значения переменных.
  1. Запускаешь Wireshark, выбираешь сетевой интерфейс, через который идёт трафик (обычно Ethernet).
  2. Ставишь фильтр захвата, чтобы не ловить всё подряд: host 192.168.0.1 (IP контроллера) или port 102 (порт S7). Но на начальном этапе можно ловить всё.
  3. Нажимаешь «Start capture».
  4. Идёшь в TIA Portal, подключаешься к контроллеру, открываешь какой-нибудь DB и наблюдаешь значения.
  5. Останавливаешь захват.
Теперь у тебя есть дамп, который надо проанализировать.

7.2.2. Фильтры отображения - наше всё​

Wireshark умеет показывать только нужные пакеты. Вот несколько фильтров, которые тебе пригодятся:
  • s7comm - показать только пакеты протокола S7 (Siemens).
  • modbus - для Modbus/TCP.
  • cipsafety или enip - для Ethernet/IP.
  • tcp.port == 102 - если Wireshark не распознал протокол, можно фильтровать по порту.
Но главная фишка - Wireshark умеет декодировать payload протокола. Для S7 он покажет, какая это функция: чтение DB, запись, управление CPU и т.д.

7.2.3. Разбор S7-пакета на примере​

Посмотрим на типичный запрос чтения DB от TIA Portal к контроллеру. В Wireshark ты увидишь примерно такую структуру:
  • TPKT (ISO 8073): Длина пакета, версия.
  • COTP (ISO 8073): Транспортный уровень S7. Здесь важно поле TPDU type: если это Data (0xf0), значит, внутри S7-сообщение.
  • S7 Communication: Собственно, протокол.
    • Header: Длина, тип сообщения (Job/Ack_Data), номер функции.
    • Parameter: Здесь указывается, что именно читаем. Например, функция 0x04 - чтение данных. Дальше идёт список адресов: область (DB, M, I, Q), номер DB (если область DB), смещение, длина.
    • Data: В запросе на чтение данных нет, в ответе - здесь будут запрошенные байты.
Если ты видишь в параметрах функцию 0x04 с адресом DB1, смещение 0, длина 100 - это чтение 100 байт из DB1. Запомни этот паттерн. В ответе придут 100 байт данных.

А теперь самое интересное: иногда инженерный софт вызывает функции, которых нет в документации. Например, функция 0x1a (запись в системную память) или 0x28 (PLC stop). Wireshark их покажет. Ты можешь подсмотреть, какие параметры передаются, и потом повторить эти запросы через свой скрипт.

7.2.4. Анализ недокументированных функций на примере Siemens​

Когда я в первый раз смотрел трафик S7, я заметил, что перед чтением больших блоков TIA Portal отправляет какой-то странный пакет с функцией 0x1f. Я полез в интернет, нашёл старые документы - оказалось, это функция "Request download" (запрос на загрузку). В новых версиях она используется реже, но в старых контроллерах она открывала доступ к загрузке проекта без пароля.

Как это работает: ты отправляешь пакет с функцией 0x1f, контроллер отвечает OK, и после этого можно читать и писать любые блоки, даже защищённые. Это, конечно, давно пофиксили, но на S7-300 с устаревшей прошивкой - до сих пор работает. И ты это можешь обнаружить только через Wireshark.

7.2.5. Modbus/TCP - простота хуже воровства​

С Modbus всё ещё проще. Wireshark отлично декодирует Modbus. Ты увидишь:
  • Transaction ID
  • Protocol ID (всегда 0 для Modbus)
  • Length
  • Unit ID
  • Function Code (03 - read holding registers, 06 - write single register, 16 - write multiple registers)
  • Data (адрес регистра, количество, значения)
Пример: ты видишь запрос с function code 03, starting address 40001, quantity 10. Это читает 10 регистров, начиная с 40001. В ответ придут 10 значений. Если ты хочешь найти, где хранится уставка давления, ты можешь подсмотреть, какие адреса читает SCADA-система, и потом сам их читать или писать.

7.2.6. Полезные приёмы работы с Wireshark​

  • Follow TCP Stream: Клик правой кнопкой на пакете -> Follow -> TCP Stream. Wireshark покажет весь диалог между клиентом и сервером в удобном виде. Можно посмотреть, как устанавливается соединение, какие команды идут.
  • Export Objects: Если в трафике передавались файлы (например, загрузка проекта), Wireshark может их вытащить.
  • Статистика -> Protocol Hierarchy: Покажет, сколько трафика какого протокола.
  • Фильтры по содержимому: Например, tcp.payload contains ff:00:00:01 - найти пакеты, в payload которых есть определённая последовательность байт. Это помогает искать конкретные команды.

7.2.7. Сниффинг в промышленной сети - риски​

Не вздумай включать Wireshark на живом работающем объекте без согласования. Во-первых, захват трафика может создать дополнительную нагрузку на коммутаторы (если ты включишь режим promiscuous и будешь ловить всё подряд, это может загрузить CPU). Во-вторых, если ты случайно отправишь какой-то пакет (например, с неправильной контрольной суммой), контроллер может зависнуть. В-третьих, сам факт прослушки может нарушать политику безопасности. Так что всегда делай это на тестовых стендах или с письменного разрешения.

7.3. pyhs220: Работа с недокументированными функциями Siemens​

Переходим к инструменту, который уже не для новичков. pyhs220 - это Python-библиотека (или, точнее, набор скриптов), которая реализует низкоуровневый доступ к контроллерам Siemens через протокол S7. В отличие от Snap7, который предоставляет высокоуровневые функции, pyhs220 позволяет формировать произвольные S7-запросы, в том числе те, которых нет в официальной документации.

7.3.1. Что такое HS и при чём тут 220?​

HS - это Hacker's Choice? Нет, скорее, отсылка к старым исследованиям. Исходники pyhs220 можно найти на GitHub, но они древние, под Python 2. Однако, их можно адаптировать. Главная ценность - это примеры формирования пакетов для нестандартных функций: останов CPU, запись в защищённую память, чтение диагностики.

7.3.2. Пример: остановка S7-300 без пароля​

В старых контроллерах (S7-300, S7-400) функция остановки CPU (PLC STOP) часто доступна без аутентификации, если не включена защита в свойствах CPU. В TIA Portal она вызывается через меню, но по протоколу это просто пакет с определённым кодом функции.

Пример кода с использованием pyhs220 (псевдокод, потому что реальный синтаксис может отличаться):

Python:
import socket
from hs220 import s7_protocol

# Создаём сокет и подключаемся к PLC
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('192.168.0.1', 102))

# Формируем S7-пакет для остановки CPU
# Функция 0x29 (PLC Control), параметры: 0x00 (STOP)
pkt = s7_protocol.create_plc_control(0x00)  # 0x00 = STOP, 0x01 = RUN

# Отправляем
sock.send(pkt)

# Получаем ответ
resp = sock.recv(1024)
# Анализируем...

Этот пакет никто не документировал официально, но он работает на многих контроллерах. Если CPU не защищён паролем или не настроен на игнорирование таких команд, он остановится. И всё.

7.3.3. Обход Know-How Protection​

В старых версиях Step7 защита know-how protection (блокировка чтения исходного кода) реализовывалась просто: в заголовке блока выставлялся флаг, и инженерный софт отказывался показывать содержимое. Но через прямое чтение памяти можно было вычитать блок целиком как бинарные данные. В pyhs220 есть примеры, как вычитать блок и потом вручную убрать флаг защиты.

Python:
# Чтение блока OB1
block_data = s7_protocol.read_block(sock, 'OB', 1)
# Далее модифицируем байты заголовка и сохраняем как обычный блок

После модификации блок можно загрузить в Step7, и он откроется без пароля. Конечно, в новых версиях TIA Portal защиту усложнили, но для старых проектов это работает.

7.3.4. Поиск уязвимостей с pyhs220​

pyhs220 удобен для фаззинга (fuzzing) - отправки случайных или некорректных запросов, чтобы проверить, не упадёт ли контроллер. Можно написать скрипт, который будет перебирать разные функции, разные параметры, и смотреть на реакцию. Если контроллер перестаёт отвечать или зависает - значит, найдена уязвимость типа DoS. Иногда такие уязвимости позволяют выполнить код, но это редкость.

7.3.5. Где брать и как использовать​

Оригинальный репозиторий pyhs220 на GitHub (от пользователя h2h) уже мёртв. Но исходники можно найти на форумах типа plcforum или в архивах. Я бы рекомендовал не тратить время на древний Python 2, а написать свой велосипед на Python 3, используя структуру пакетов из документации Snap7 или из открытых спецификаций. Snap7, кстати, тоже умеет отправлять пользовательские PDU, но для этого нужно копаться в его внутренностях.

Альтернатива: использовать Scapy с поддержкой S7 (есть плагины). Но это для тех, кто любит сложности.

7.4. Metasploit: Тяжёлая артиллерия​

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

7.4.1. Модули для Siemens​

В Metasploit есть несколько модулей для работы с Siemens PLC. Самый известный - auxiliary/admin/scada/siemens_s7_300_400_cpu_command. Он позволяет отправлять команды на CPU (старт, стоп, сброс) через недокументированные функции.

Использование:
  1. Запускаешь msfconsole.
  2. Выбираешь модуль: use auxiliary/admin/scada/siemens_s7_300_400_cpu_command.
  3. Задаёшь параметры: set RHOSTS 192.168.0.1, set COMMAND stop (или start).
  4. Запускаешь: run.
Если контроллер уязвим, он остановится. Это проверено на многих S7-300 с прошивками до 2012 года.

7.4.2. Модули для Modicon (Schneider)​

Есть модуль auxiliary/admin/scada/modicon_stop для остановки контроллеров Modicon через Modbus. Работает просто: отправляет специальную команду в функциональный код 90 (нестандартный). Если контроллер не защищён, он уходит в STOP.

7.4.3. Модули для чтения памяти​

Есть модули для чтения регистров Modbus: auxiliary/scanner/scada/modbus_find_holding_registers. Он сканирует диапазон адресов и читает значения, помогая составить карту памяти.

7.4.4. Написание собственного модуля Metasploit​

Если ты хочешь автоматизировать какой-то специфический эксплойт, можешь написать свой модуль на Ruby. Metasploit предоставляет удобные API для работы с сокетами, таймерами, шелл-кодами. Но для PLC это редко нужно, обычно хватает готовых модулей и вспомогательных скриптов.

7.4.5. Ограничения Metasploit​

Главное ограничение: Metasploit - это инструмент для атак, и его использование без разрешения незаконно. Кроме того, многие модули заточены под старые версии прошивок. На новых контроллерах (S7-1500, M580 с активированной защитой) они не сработают. Но для проверки унаследованных систем - самое оно.

7.5. Другие нишевые инструменты​

Кроме трёх китов (Wireshark, pyhs220, Metasploit), есть ещё куча специализированных утилит, которые стоит знать.

7.5.1. plcscan​

Скрипт на Python для сканирования промышленных протоколов. Он умеет определять тип контроллера, версию прошивки, открытые сервисы. Используется для разведки: ты запускаешь его в сети и получаешь список всех PLC с их характеристиками.

Bash:
python plcscan.py 192.168.0.0/24
Вывод:

Код:
192.168.0.1:102 Siemens S7-300, CPU 315-2 PN/DP, Firmware V3.2
192.168.0.2:502 Modicon M340, firmware 2.5

Очень полезно для инвентаризации сети.

7.5.2. modpoll​

Утилита командной строки для опроса Modbus-устройств. Поддерживает все основные функции: чтение/запись регистров, битов. Легко встраивается в скрипты.

Bash:
modpoll -m tcp -a 1 -r 40001 -c 10 192.168.0.2
Читает 10 holding registers с адреса 40001 с устройства с unit ID 1.

7.5.3. cpppo​

Библиотека Python для работы с Ethernet/IP и CIP. Позволяет читать и писать тэги в контроллерах Rockwell. Очень мощная, поддерживает не только базовые типы, но и структуры, массивы.

Python:
from cpppo.server.enip import client
with client.connector(host='192.168.0.3') as conn:
    result = conn.read('@4/100/3')  # пример чтения тэга

7.5.4. ISF (Industrial Exploitation Framework)​

Рамка для эксплуатации промышленных систем, разработанная авторами известной книги "Hacking Exposed: Industrial Control Systems". Написана на Python, включает модули для сканирования, эксплуатации уязвимостей, работы с протоколами. Позиционируется как аналог Metasploit, но специализированный для АСУ ТП. К сожалению, проект развит слабо, но исходники доступны и могут быть полезны для изучения.

7.5.5. S7Scan​

Утилита для сканирования Siemens S7. Умеет определять тип CPU, состояние (RUN/STOP), версию, а также пытается подобрать пароль по словарю (через недокументированные функции). Полезно для аудита.

7.6. Комбинирование инструментов: Реальный сценарий​

Давай представим, что ты проводишь пентест завода. Как ты будешь использовать все эти инструменты?
  1. Разведка: Запускаешь plcscan по всей подсети, получаешь список контроллеров.
  2. Пассивный анализ: Включаешь Wireshark на зеркальном порту коммутатора, чтобы посмотреть, какие протоколы используются, какие адреса читаются. Собираешь несколько часов трафика.
  3. Активный анализ (осторожно!): Выбираешь один некритичный контроллер (например, на складе, а не в реакторе). Пробуешь через modpoll или snap7 прочитать несколько регистров, посмотреть, есть ли отклик.
  4. Поиск уязвимостей: Пробуешь через Metasploit модуль siemens_s7_300_400_cpu_command на старом S7-300. Если останавливается - есть проблема.
  5. Глубокий реверс: На тестовом стенде (или на отключённом оборудовании) используешь pyhs220 для фаззинга, ищешь новые недокументированные функции.
  6. Отчёт: Документируешь все находки, даёшь рекомендации по защите.

Часть 8. Легализация: Зачем это нужно белому хакеру?​

Может показаться, что мы тут собрались всё ломать. А вот хрен там. Самые крутые инженеры используют эти техники каждый день, просто не всегда об этом говорят.
  • Восстановление утерянного проекта. Бывало? Жесткий диск сгорел, бэкапов нет, а станок работать должен. Загружаем из контроллера всё, что можно. Потом, используя реверс-инжиниринг и эти дампы памяти, мы можем частично восстановить логику или, по крайней мере, понять, какие входы/выходы используются.
  • Аудит безопасности. Ты должен знать, может ли твой контроллер быть остановлен извне. Ты должен проверить, можно ли изменить уставку давления без авторизации. И лучший способ это проверить - сделать это самому, используя те же инструменты, что и потенциальный злоумышленник.
  • Диагностика. Контроллер глючит, но нет проекта. Мы можем смотреть, как меняются значения во времени, искать залипания битов, аномальные скачки данных. Это даст подсказку, где искать проблему.

Часть 9. Мораль сей басни​

Мы с тобой рассмотрели, как можно залезть в кишки ПЛК, не зная пароля и логики. Это не просто дырка - это архитектурная особенность. Промышленные протоколы разрабатывались в эпоху, когда об интернете вещей даже не слышали, и главным требованием была надежность и скорость, а не безопасность. Вендоры много лет закладывали мины под свои системы, надеясь на изоляцию сетей (air gap). Сейчас эти мины взрываются на каждом шагу.

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

Заключение: Почему это вообще возможно?​


Всё это возможно по одной простой причине: протоколы промышленной автоматизации создавались для надёжности и детерминизма, а не для безопасности. В 80-е и 90-е годы, когда закладывалась архитектура Modbus, Profibus, S7, никто не предполагал, что эти сети будут торчать в интернете. Заводы были изолированы физически, и главной угрозой считался электрик, который ошибётся с проводами, а не хакер из другой страны.

Протоколы были закрытыми (проприетарными) не для защиты от злоумышленников, а для конкурентного преимущества. Вендоры надеялись, что сложность форматов сама по себе отпугнёт любопытных. Это называется «безопасность через неясность» (security by obscurity). И мы все знаем, чем это заканчивается: рано или поздно находится энтузиаст с Wireshark и паяльником, который всё раскуривает.

Когда промышленные сети начали переходить на Ethernet, протоколы просто завернули в TCP/IP, не добавив ни грамма аутентификации или шифрования. Modbus TCP - это Modbus RTU, упакованный в TCP-пакет. S7 over Ethernet - это старый добрый S7, только поверх TCP-порта 102. Всё те же функциональные коды, всё те же адреса.

Да, сейчас вендоры добавляют функции безопасности: пароли на загрузку проекта, списки доступа (IP-фильтрация), шифрование (например, S7-1500 с опцией Security), ролевые модели. Но:
  1. Обратная совместимость. Миллионы старых контроллеров (S7-300, S7-400, Modicon Quantum, старые CompactLogix) стоят на объектах и будут стоять ещё лет 20. Их прошивки не менялись, и они как были открытыми, так и остались.
  2. По умолчанию - открыто. На новых контроллерах защита часто отключена «для удобства». Инженеру нужно быстро залить проект, и он не хочет возиться с паролями. Потом забывает включить.
  3. Кривые реализации. Даже там, где защита есть, её можно обойти. Пример: в старых версиях S7-1200 пароль на загрузку хранился в открытом виде или в виде простого хеша в памяти, и его можно было считать по недокументированной функции.
Так что мы имеем дело с огромным парком уязвимых устройств, и это не изменится в ближайшее десятилетие.

Итог и напутствие​

Мы с тобой разобрали тему чтения тегов контроллеров через недокументированные функции от А до Я. Ты узнал, как устроена память PLC, какие протоколы используются, как работают основные библиотеки, как искать переменные без документации и как этим пользоваться.

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

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

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