Реверс plz для Motorolla ACE3600
Jun 20, 26IEC104MS License Key Generator — Reverse Engineering Report
Первичный этап
Через binwalk расшифровываем поток zlib.
└─$ binwalk -e exa001.plz
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
64 0x40 Zlib compressed data, best compression
А полученный файл прогоняем ещё раз - это у нас ELF файл для линукса.
└─$ binwalk exa001.pl
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 ELF, 32-bit MSB relocatable, PowerPC or cisco 4500, version 1 (SYSV)
На тестовых данных погоняли, теперь пробуем с нашим файлом:


Что у нас есть
Библиотека: ace104ms.plz (архив) → ELF-файл “40” (PowerPC big-endian, 32-bit)
Декомпилятор: Hex-Rays (40.c, 44579 строк)
Драйвер: IEC104MS API Driver ver.4.2 (Apr 28 2017)
Процессор: Motorola PowerPC (RTU ACE3600)
1. Общая схема лицензирования
При инициализации драйвера вызывается Vira_IEC104MS_Ini() (40.c:2771), которая, среди прочего, вызывает license_check() (40.c:8121).
Последовательность проверки:
- Читается CPU ID из аппаратного регистра 0x400FC014 (серийный номер)
- Читается 16-байтный ключ из конфигурационной таблицы PLC (M870iniTable + смещение 0xA0) — так называемый “Ladder key”
- Если существует файл “iec104lk.29” — ключ читается из него и ПЕРЕОПРЕДЕЛЯЕТ ключ из Ladder (приоритет файла выше)
- Ключ (32-символьная HEX-строка) проверяется функцией key_valid()
- key_valid() перебирает три типа лицензии:
- IEC104MASTER4.2 → режим 1 (Master)
- IEC104SLAVE_4.2 → режим 2 (Slave)
- IEC104PEER__4.2 → режим 3 (Master+Slave)
- Если ни один не подошёл → режим 0 (DEMO, ограниченное время)
2. Детальный разбор функций
2.1 license_check() — 40.c:8121
1. strncpy(ace_cpu_serial_number_string, 0x400FC014, 20) — читаем CPU ID
2. memcpy(buf, M870iniTable + 0xA0, 16) — ключ из конфигурации
3. Конвертация 16 байт → 32 символа HEX (sprintf "%02X")
4. key_valid(hex_string, &result_ladder) — проверка Ladder
5. key_in_File(hex_string) — ищем файл iec104lk.29
└─ если найден: key_valid(hex_string, &result_file) — проверка File
6. Итог: result = result_file | result_ladder
2.2 key_in_File() — 40.c:8022
- Открывает файл “iec104lk.29” через mffs_OpenFile
- Ищет подстроку “IEC104License” в содержимом
- Парсит формат:
IEC104License = <32-char-hex-key> - Копирует ключ в выходной буфер
2.3 key_valid() — 40.c:8081
Формирует строки для проверки:
sprintf(buf, "%s%d.%d", "IEC104MASTER", 4, 2); // "IEC104MASTER4.2"
sprintf(buf, "%s%d.%d", "IEC104SLAVE_", 4, 2); // "IEC104SLAVE_4.2"
sprintf(buf, "%s%d.%d", "IEC104PEER__", 4, 2); // "IEC104PEER__4.2"
Для каждой вызывает key_check(key, label, 0, 0). Если key_check возвращает 0 — лицензия валидна для этого типа.
2.4 key_check() — 40.c:44412 (КЛЮЧЕВАЯ ФУНКЦИЯ)
Алгоритм проверки:
- Если a3 == 0, CPU ID читается из 0x400FC014 (аппаратный регистр)
- Формируется буфер v31[12] (48 байт):
memset(v31, 0, 41); // очистка 41 байта memcpy(v31, cpu_id, strlen(cpu_id)+1); // копируем CPU ID + '\0' memcpy(v31 + strlen(cpu_id), label, strlen(label)+1); // ВНИМАНИЕ: второй memcpy пишет ПОВЕРХ '\0' от первого! // Результат: ПРЯМАЯ конкатенация без разделителя: // "085SLW00TV" + "IEC104SLAVE_4.2" + '\0' - Замена нулевых байт в первых 40 байтах на ‘*’ (0x2A):
"085SLW00TVIEC104SLAVE_4.2\0\0\0..." → "085SLW00TVIEC104SLAVE_4.2*********..." - Принудительный ‘\0’ в байте 40 → strlen возвращает 40
- Вызов Hash_128(v31, 40, &hash_output)
- Преобразование 16-байтного хеша в 32-символьную HEX-строку
- Посимвольное сравнение с переданным ключом
3. Алгоритм Hash_128 — 40.c:44327
Это 128-битная хеш-функция на основе TEA (Tiny Encryption Algorithm), работающая в режиме, похожем на CBC-MAC.
Параметры:
- Вход: 40 байт (5 блоков по 8 байт, каждый блок = 2 × uint32 BE)
- Состояние: (v21, v22, v23, v24) — 4 × uint32 = 128 бит
- Константа: delta = 0x61C88647 (отрицательное значение TEA delta)
- Раундов: 32 на каждую фазу
Для каждого 8-байтного блока (w0, w1):
Фаза 1 — TEA-encipher (v23, v24) с ключом (v21, v22, w0, w1): for i in 0..31: sum -= delta v23 += ((v24«4)+v21) ^ (v24+sum) ^ ((v24»5)+v22) v24 += ((v23«4)+w0) ^ (v23+sum) ^ ((v23»5)+w1) state[2] ^= v23; state[3] ^= v24
Фаза 2 — TEA-encipher (v21, v22) с ключом (w0, w1, v23_new, v24_new): for i in 0..31: sum -= delta v21 += ((v22«4)+w0) ^ (v22+sum) ^ ((v22»5)+w1) v22 += ((v21«4)+v23) ^ (v21+sum) ^ ((v21»5)+v24) state[0] ^= v21; state[1] ^= v22
Результат: (v21, v22, v23, v24) — 16 байт → 32 символа HEX.
4. Верификация на образцах
У меня были примеры эталоных файлов, на них я проверил генератор - он выдаёт точные совпадения.
5. Ключевая находка (баг/особенность)
При построении входного буфера в key_check():
memcpy(v31, cpu_id, strlen(cpu_id)+1); // "085SLW00TV\0"
memcpy(v31 + strlen(cpu_id), label, strlen(label)+1); // поверх '\0'!
Второй memcpy пишет ПОВЕРХ нуль-терминатора CPU ID. В результате строка label приклеивается к CPU ID напрямую, без какого-либо разделителя:
“085SLW00TV” + “IEC104SLAVE_4.2” (без ‘\0’ между ними!)
Если бы разделитель сохранялся (распространённая ошибка при анализе), хеш был бы совершенно другим. Именно это дало неверный результат с первой попытки.
6. Применение
Генератор: license/keygen.py
python license\keygen.py
Без аргументов — самопроверка на эталонных образцах. С аргументом — генерация всех трёх типов лицензий.
Формат вывода готов для копирования в файл iec104lk.29:
; CPU ID: 085SLW00TV ; Driver version 4.2
IEC104License = полученный_хэш ; Slave mode
Файл размещается в файловой системе RTU. При загрузке драйвер читает его и активирует соответствующий режим лицензии.
Ссылка на репозиторий с расшифровкой - ACE3600-iec104-plz.