
Инструмент для манипуляции архивными файлами PLC Schneider Electric
Данный инструмент может извлекать и собирать заново архивные файлы ПЛК, используемые несколькими ПЛК Schneider Electric, включая как минимум M580, M340 и Quantum. Архивные файлы с расширением .sta на самом деле являются zip-архивами, содержащими несколько файлов. Внутри архива самым важным файлом является Station.apx. Содержимое этого файла передается в/из ПЛК во время (полной) загрузки/выгрузки программы. Естественно, он содержит фактический исполняемый код программы ПЛК. Но он также содержит всю информацию, необходимую для редактирования программы ПЛК в Control Expert Classic (или Unity Pro). Формат файлов .apx является проприетарным, и о нем доступно мало информации. Немного информации можно найти на Liras en la red и Team82, но эта работа идет глубже, чем их публикации. С помощью этого инструмента различные "разделы" из Station.apx могут быть извлечены (и при необходимости распакованы) для изучения. Инструмент также может собрать Station.apx из ранее извлеченных — и, возможно, измененных — данных разделов и метаданных. И этот собранный файл откроется без ошибок в Control Expert Classic / Unity Pro, если внесенные изменения не нарушают "логику разделов".
У этого проекта никогда не было грандиозного плана. Время от времени я использую ПЛК Schneider Electric, и мне было интересно, как они работают (или не работают) на более фундаментальном уровне. Как оказалось, это исследование было достаточно сложным, чтобы развлекать меня (вероятно, как решение кроссвордов для обычных людей), и в итоге я создал этот инструмент.
Инструмент делает все возможное для извлечения и сборки файлов, с которыми он работает. Однако пользователь должен знать, что инструмент разрабатывался с:
Использование собранных файлов на реальном ПЛК может вызвать проблемы, особенно если вы изменили разделы или метаданные. Возможно даже, что это может привести к выходу ПЛК из строя. Поэтому загружайте измененные архивные файлы в ПЛК на свой страх и риск.
Инструмент используется из командной строки с интерпретатором python:
python apxutil.py -h
usage: apxutil.py [-h] [-f filaname-apx] [-F filaname-sta] [-e dir] [-E dir] [-a manifestpath] [-A manifestpath] [-d]
[-x] [-B] [-r]
Tool to manipulate Schneider Electric .sta and .apx files
options:
-h, --help show this help message and exit
-f, --apxfile filaname-apx
.apx file to read or write
-F, --stafile filaname-sta
.sta file to read or write
-e, --extract-apx dir
extract contents of the Station.apx file
-E, --extract-sta dir
extract contents of the .sta file
-a, --assemble-apx manifestpath
create Station.apx file based on apx_manifest.ini
-A, --assemble-sta manifestpath
create a .sta file from files in sta_manifest.ini
-d, --decompress decompress Station.apx sections that are compressed
-x, --hexdump print hexdump of Station.apx with header information
-B, --include-apd include Station.apd when creating .sta archive
-r, --restart-offsets
restart offsets at 0 for each section in hexdump print (useful for diffing)
Справка должна быть достаточно понятной. Как правило, краткие опции с заглавными буквами работают с файлами .sta, а строчные — с файлами .apx. Несколько примеров использования приведены ниже.
Чтобы извлечь содержимое файла .sta в каталог "extracted", используйте:
python apxutil.py -F archive.sta -E extracted
Чтобы извлечь содержимое Station.apx в каталог "contents", используйте:
python apxutil.py -f extracted/BinAppli/Station.apx -e contents -d
Опция "-d" означает, что сжатые разделы будут распакованы, но ее можно опустить, если нужны (все) необработанные данные разделов.
Чтобы собрать Station.apx, используйте:
python apxutil.py -f extracted/BinAppli/Station.apx -a contents
Это соберет Station.apx на основе apx_manifest.ini, найденного в каталоге contents, сжимая данные разделов, если они были предварительно распакованы. Размеры разделов и CRC будут пересчитаны, а не считаны из apx_manifest.ini.
Чтобы собрать файл .sta, используйте:
python apxutil.py -F modified-archive.sta -A extracted
По умолчанию при этом будет опущен файл Station.apd (который, по-видимому, в основном служит для проверки целостности заголовка файла Station.apx). Если вы хотите его включить, добавьте опцию "-B". Но это может привести к тому, что Control Expert Classic не сможет открыть файл архива проекта.
Чтобы напрямую "просмотреть" содержимое файла Station.apx, можно использовать следующую команду:
python apxutil.py -F modified-archive.sta -x
Это выведет "канонический hexdump" файла Station.apx: вы увидите смещения, шестнадцатеричные данные и ASCII-данные по 16 байт за раз, включая метаданные. Опция "-d" может использоваться для распаковки сжатых разделов (но учтите, что смещения в этом случае теряют смысл). Опция "-r" позволяет перезапускать смещение с 0 для каждого раздела. Это полезно, если вы внесли изменения и хотите иметь возможность "сравнить" их без смещений.
Если вы хотите понять формат файла APX (на том уровне, на котором понимаю его я и этот инструмент), лучше всего посмотреть исходный код apxutil.py. Код не должен быть слишком сложным для чтения даже при небольшом опыте программирования. Но он может вызвать тошноту у настоящих программистов. В любом случае, здесь дается общий обзор формата файла. Файл APX начинается с 32-байтового заголовка файла. Остальная часть файла разделена на различные разделы, каждый со схожей структурой. Каждый раздел начинается с заголовка раздела, длина которого зависит от типа раздела (определяется в начале заголовка). За заголовком раздела следует RTE (заголовок). После RTE идут данные раздела (если они присутствуют). А после данных раздела начинается следующий раздел (заголовок). Похоже, что заголовок раздела больше связан с форматом Station.apx, а RTE больше связан со средой выполнения (RunTime Environment или RealTime Environment?), но это нечеткое различие.
Заголовок файла APX имеет длину 32 байта и начинается с текста ASCII "APX", за которым следуют некоторые метаданные. Возможно, самое важное: он определяет тип и размер используемого RTE. Тип 0x02 — это то, что я видел. Вероятно, тип 0x01 предназначен для 16-битного адресного пространства, а тип 0x02 — для 32-битного, но это далеко не точно. Есть несколько неизвестных полей, которые, вероятно, имеют некоторое значение. Пара 32-битных значений и одно 16-битное значение "повторяются" в разделе "SD" 0x0001, но их значение неизвестно. Последние 11 байт, по-видимому, всегда равны нулю. Пример ниже (в каноническом формате hexdump с метаданными):
00000000 41 50 58 00 00 01 02 01 10 01 10 60 f6 c3 30 00 |APX........`..0.| ApxFileHeader(magic=0x00585041, version_maybe=0x0100, rte_type=0x02, header_count_maybe=0x01, sdsection_total_size=0x0110, rte_size=0x10, sdsection_39_4=0x30c3f660, sdsection_35_4=0x0b926500, sesection_8_2=0x0106, zero_pad_21_11=0000000000000000000000)
00000010 65 92 0b 06 01 00 00 00 00 00 00 00 00 00 00 00 |e...............|
Заголовок раздела APX начинается с 16-битного значения, определяющего его тип. Типы 0x0001–0x0004, по-видимому, допустимы, но я видел только типы 0x0000, 0x0001 и 0x0002. Длина заголовка раздела зависит от типа. Для 0x0000 длина составляет 8 байт, для 0x0001–0x0002 — 16 байт, а для типов 0x0003–0x0004 — 24 байта. Номер раздела задается 16-битным значением со смещением 4 байта. Для типов разделов 0x0000 и 0x0001 это все, что я знаю, и у них нет данных раздела. Для типа раздела 0x0002 32-битное значение со смещением 10 байт указывает размер данных раздела в файле Station.apx.
Заголовок RTE, следующий за заголовком раздела, имеет длину 16 байт (по крайней мере, для типа 0x02). Значение первого 32-битного значения неизвестно (но здесь оно названо "block_count"). 32-битное значение, начинающееся со смещения 4, — это размер данных раздела (из заголовка раздела) минус 1, если у раздела есть данные. Если у раздела нет данных, значение неизвестно (и раздел 0x0011 нарушает это правило). 32-битное значение, начинающееся со смещения 8 байт, — это атрибуты/флаги, большинство из которых имеют неизвестное значение. Некоторые мысли перечислены в конце apxutil.py. 16-битное значение, начинающееся со смещения 12 байт, — это контрольная сумма CRC16/Kermit данных раздела (и опять же, раздел 0x0011 нарушает это правило). 3 старших бита байта 14 определяют область памяти, а 5 младших — фолио памяти. Последний байт 15, вероятно, не используется.
Данные раздела в целом представляют собой "необработанные" данные, которые могут содержать исполняемый код, информацию о проекте, сжатые данные и т.д. Похоже, что если установлен бит 13 в атрибутах RTE, то данные раздела сжаты. Наблюдаемые типы сжатия: zip/"pk", zlib с нестандартным заголовком и "raw deflate".
Хотя все разделы имеют свое значение, похоже, что разделы 0x0001 "RT" и 0x0011 "SD" являются основными. Они, по-видимому, связаны с RTE и фактическим расположением памяти среды выполнения ПЛК. Частичная декодировка этих разделов включена в конец apxutil.py, но в настоящее время не используется самим инструментом.
Здесь много "низко висящих" плодов, если кто-то хочет провести исследование. Некоторые открытые вопросы: