Что это за приложение?
ValedoMotion — ПК-компонент реабилитационной системы Valedo швейцарской компании Hocoma AG (сейчас входит в DIH Group). Предназначен для терапии пациентов с хроническими болями в спине.
Как работает:
- На тело пациента крепятся 3 беспроводных BLE-датчика (грудь, поясница, бедро)
- Датчики передают данные о движении в реальном времени через Bluetooth-донгл
- ПО показывает движение пациента визуально, проводит через игровые упражнения
- Врач получает объективные метрики: амплитуду движений (ROM), прогресс терапии, отчёты
Архитектура: .NET Framework 4.5 + WPF + Prism MVVM + DevExpress 14.2 + Entity Framework 6. Два процесса на ПК: ValedoMotion (GUI) и IMUSensorAPI.ServiceHost (работа с датчиками через BLE-донгл), общаются через WCF Named Pipes.
Дополнительные модули
| Модуль | Назначение |
|---|---|
| ValedoShape | Тренировка с биологической обратной связью (отдельная установка, регистрируется в реестре) |
| ValedoMPI | Обмен данными с Medical Practice Integration |
Где хранится лицензионный ключ?
Путь к файлу
|
|
Как образуется путь (App.xaml.cs:19-27):
- Читается
CompanyNameиProductNameиз метаданных сборки:Hocoma AG→Hocoma_AG,ValedoMotion→ValedoMotion - Путь:
Environment.SpecialFolder.CommonApplicationData+\Hocoma_AG\ValedoMotion
Структура файла
|
|
Примечание: хотя
[XmlRoot]объявляет namespacehttp://www.hocoma.com/valedomotion, сериализация черезSerialize(true, true)вырезает все пространства имён (XmlSerializerNamespaces.Add("", "")). Файл на диске — без xmlns уValedoConfiguration.
Как записывается в файл
- Пользователь вводит ключ в поле License Manager → нажимает «Apply»
LicenseManagerPresenter.SaveLicenseKeyToConfig()(LicenseManagerPresenter.cs:184):- Отключает все датчики
- Отправляет ключ через WCF Named Pipes в процесс IMUSensorAPI.ServiceHost для проверки
- При успехе сохраняет ключ и список сенсоров в
SensorWhitelist
SensorWhitelist.LicenseKey = key→ConfigurationService.Save()(SensorWhitelist.cs:28-32)ConfigurationService.Save()(ConfigurationService.cs:103-115):File.WriteAllText()— запись XMLxmlSigner.Sign()— подпись RSA (защита от редактирования блокнотом)
Защита от подделки
После сохранения файл подписывается XML-цифровой подписью — enveloped signature (XML DSig), RSA, при каждом сохранении генерируется новый эфемерный ключ (new RSACryptoServiceProvider()). Публичный ключ встраивается в подписанный файл через RSAKeyValue — подпись самодостаточна.
При загрузке (в конструкторе ConfigurationService) вызывается ReadConfig() → ValidateXml() → XMLValidator.Validate(). Если подпись отсутствует или недействительна — выбрасывается XmlException: "Missing or invalid xml signature".
Перенос с бэкапа: файл можно скопировать как есть — подпись встроена в сам XML и не зависит от тайминга/пути. Но практичнее через UI:
- Откройте старый
Configuration.xml - Скопируйте значение атрибута
License Key="..." - Вставьте его в интерфейс License Manager на новой установке
- Нажмите «Apply» — приложение само проверит ключ, определит датчики, перезапишет файл и поставит новую подпись
Как проверяется лицензионный ключ?
Коротко: да, привязан к MAC-адресам сенсоров
Ключ — ровно 15 символов (пример: GEspqbSBJMnc4WN). Кодирует частичную информацию о MAC-адресах 3 конкретных датчиков с помощью подстановочного шифра.
Если у вас новые сенсоры (другие MAC-адреса) — старый ключ не подойдёт. Ключ привязан к конкретным физическим датчикам.
Полная схема проверки
ValedoMotion не общается с датчиками напрямую. Он шлёт запросы в отдельный процесс-сервис (Hocoma.IMUSensorAPI.ServiceHost.exe), который работает на том же ПК и взаимодействует с датчиками через BLE-донгл. Общение ValedoMotion ↔ ServiceHost идёт через WCF Named Pipes (net.pipe://localhost/Hocoma/IMUSensorService/API/).
Валидация ключа происходит на ПК, в процессе IMUSensorAPI.ServiceHost, но требует наличия физических датчиков в BLE-эфире — без их MAC-адресов проверять нечего.
|
|
Реальный алгоритм проверки (из нативной DLL)
Алгоритм реконструирован по декомпилированному коду Hocoma.Common.SensorValidation.dll (Hex-Rays, строки 452–824):
Шаг 1. Таблицы подстановки — 5 таблиц по 16 символов:
| Индекс | Таблица |
|---|---|
| 0 | 627GDFMKnp5bxPuY |
| 1 | Zv4K9p5jAEWTUScB |
| 2 | Nf5ryP78Hsk3WSB4 |
| 3 | HCa7xR64uhpJZKAW |
| 4 | tJTdS9rYghqpM5PN |
Каждая таблица — это перестановка 16 символов, соответствующая hex-цифрам 0–f. Функция invmap(c, codeNum) (SensorValidator::invmap, строка 832):
- Находит позицию символа
cв таблицеcodes[codeNum](0–15) - Возвращает hex-цифру:
0→'0', 1→'1', ..., 9→'9', 10→'a', ..., 15→'f' - Если символ не найден —
'-'
Шаг 2. Декодирование ключа — 15 символов ключа на 3 шаблона по 12 hex-цифр:
|
|
Каждый символ ключа ищется в таблице с номером position % 5, полученная hex-цифра записывается в один из трёх 12-символьных шаблонов (invkey[0..2]).
Шаг 3. Позиции в шаблоне — карта индексов (indices[3][5]):
| Шаблон | Позиции в шаблоне | Смысл в MAC |
|---|---|---|
| invkey[0] | 0, 1, 2, 3, 6 | Байты 0–1 + старший полубайт байта 3 |
| invkey[1] | 0, 1, 2, 3, 7 | Байты 0–1 + младший полубайт байта 3 |
| invkey[2] | 0, 1, 2, 3, 8 | Байты 0–1 + старший полубайт байта 4 |
Незаполненные позиции (4, 5, 9, 10, 11) остаются '0' — они не участвуют в сравнении.
Шаг 4. Сравнение с датчиками — каждый из трёх шаблонов сравнивается со всеми обнаруженными MAC-адресами (из которых удалены пробелы/двоеточия). Проверяются только 5 указанных позиций. Если все 5 совпадают — датчик засчитывается.
Шаг 5. Результат: три строки с ID датчиков, прошедших проверку, разделённые /. Невалидные предваряются -.
Python: генерация и проверка ключа
|
|
Принцип работы кода:
generate_license_key: для каждого из 3 слотов берёт 5 hex-цифр MAC по картеINDICES, преобразует каждую hex-цифру (0–f) в позицию (0–15), выбирает символ из таблицыCODES[col][pos]. Результат — 15-символьная строка.validate_license_key: обратная операция — черезinvmapвосстанавливает hex-цифры из символов ключа, формирует 3 шаблона по 12 hex-цифр, сравнивает каждый с обнаруженными MAC-адресами на 5 позициях. Ключ валиден только если все 3 слота нашли совпадение.- Ограничение: сверяются только 5 из 12 позиций MAC, поэтому ключ не содержит полного MAC-адреса — только первые 2 байта + 1 полубайт.
Ожидается ровно 3 датчика
|
|
Примечание о двойном механизме: в кодовой базе присутствует также C# класс
LicenseGenerator(Hocoma.Valedo.Licensing/LicenseGenerator.cs), использующий MD5 и 64-байтный XOR-обфусцированный ключ. Похоже, это либо устаревший механизм, либо внутренний инструмент генерации ключей, не используемый в реальной валидации. Фактическая проверка ключа происходит исключительно в нативной DLL через описанный выше подстановочный шифр.
Что проверяется при старте приложения
|
|
Кнопка «Далее» на стартовом экране неактивна, пока не введён и не проверен валидный ключ (если используется ValedoMotion). Если установлен только ValedoShape — ключ не требуется.
Дополнительная система: USB-флеш-ключ
Помимо основного механизма, существует вторая, независимая система — сканирование USB-накопителей (Hocoma.Framework.Licensing/LicenseService.cs):
- Фоновый поток раз в 5 секунд проверяет все сменные диски
- Ищет файл с заданным именем (
keyFile) в корне флешки - Читает и расшифровывает Rijndael (AES-192, CBC, ISO10126 padding):
- Passphrase:
"HocomaSecretPassPhraseIsTheBest" - Salt:
"HocomaSalt" - IV:
"iHSoNVtdfg7BRXga" - PBKDF2 iterations: 1 (очень мало — слабая защита)
- Passphrase:
- Если расшифрованное содержимое совпадает с
privateKey— флагisLicenseKeyEnabled = true - Если начинается с
privateKey— остаток становитсяServiceId
Эта система, видимо, использовалась для корпоративных USB-ключей лицензий (сервис-инженеры).
Итоговая таблица
| Что | Где / Как |
|---|---|
| Файл с ключом | C:\ProgramData\Hocoma_AG\ValedoMotion\Configuration.xml |
| Элемент | Атрибут License Key="..." внутри <License> |
| Привязка к железу | К MAC-адресам 3 BLE-датчиков |
| Алгоритм | Подстановочный шифр: каждый из 15 символов ключа → позиция в одной из 5 таблиц-перестановок → hex-цифра (0-f). Формируется 3 шаблона по 5 hex-цифр, сравнивается с позициями {0,1,2,3,6}, {0,1,2,3,7}, {0,1,2,3,8} MAC-адресов датчиков |
| Где проверка | На ПК, в процессе IMUSensorAPI.ServiceHost, через WCF Named Pipes → нативная C++ DLL |
| Родная DLL | Hocoma.Common.SensorValidation.dll — нативный C++ (Visual C++), декомпилирован Hex-Rays |
| ————————————————————————- | —————————————————————————————————————————————————————————————————————————————————— |
| Секретный ключ | Таблицы подстановки: 5 строк по 16 символов, вшиты в DLL как константные массивы codes[5][16]. Никакого «секретного ключа» в привычном смысле — только таблицы-перестановки |
| Защита файла | XML Digital Signature (RSA, эфемерный ключ на каждое сохранение) |
| Резервная система | USB-флешка с AES-192 зашифрованным файлом, сканируется раз в 5 сек |
| Крипто флешки | Rijndael, passphrase: HocomaSecretPassPhraseIsTheBest, salt: HocomaSalt, IV: iHSoNVtdfg7BRXga |
| Блокировка старта | Кнопка «Далее» неактивна без валидного ключа и лицензированных сенсоров |
Практическая инструкция: перенос лицензии на новую установку
- Найдите старый компьютер или бэкап, откройте:
1C:\ProgramData\Hocoma_AG\ValedoMotion\Configuration.xml - Найдите строку вида
License Key="GEspqbSBJMnc4WN"(ровно 15 символов) — скопируйте значение ключа - На новой установке запустите ValedoMotion
- На стартовом экране (License Manager) вставьте ключ в поле ввода
- Убедитесь, что BLE-донгл подключен и датчики включены
- Нажмите «Apply» — приложение само проверит ключ, определит датчики и сохранит конфигурацию
Важно: ключ сработает только с теми же самыми физическими датчиками (их MAC-адресами), для которых он был выдан. Если у вас новые датчики — нужен новый ключ от Hocoma/DIH.
P.S. Ошибка «Login not successful» при загрузке
Если после ввода лицензии и обнаружения датчиков (видны индикаторы заряда в заголовке) их индикаторы внезапно пропадают и появляется диалог «Error logging in» — проблема не в лицензии и не в датчиках.
Причина
При нажатии «ValedoMotion» на экране логина (LoginPresenter.cs:114) запускается таймер на 10 секунд. За это время должно произойти:
- Старт
IMUSensorAPI.ServiceHost.exe— работа с датчиками (BLE) - Старт
Hocoma.ExerciseHost.Main.exe— игровой движок (GAL) - ExerciseHost подключается к ValedoMotion через WebSocket (
ws://localhost:46544) и WCF Named Pipe
Если ExerciseHost не запускается или не подключается за 10 секунд — таймер срабатывает:
|
|
Корневая причина: ExerciseHost (GAL-процесс) не стартовал. Чаще всего — завис глобальный мьютекс Global\58e916b7-b43a-49ec-81ee-c4e8642aaf96 от предыдущего аварийного завершения. ValedoMotion проверяет мьютекс и думает, что ExerciseHost уже работает (ExerciseHostController.cs:56), поэтому не запускает его заново.
Решение
- Открой Диспетчер задач, найди процессы
Hocoma.ExerciseHost.Main.exe— заверши их все - Запусти
Hocoma.ExerciseHost.Main.exeвручную из папки установки (окно будет скрыто ValedoMotion) - Запусти ValedoMotion — ошибки не будет
Или просто перезагрузи компьютер — мьютекс освободится, ValedoMotion сам поднимет ExerciseHost при следующем запуске.
Но в моём случае это не помогало, и я просто добавил файл в автозагрузку.
Архитектура двух процессов
|
|
Оба процесса — обычные .exe, не службы. ExerciseHost запускается ValedoMotion через Process.Start() и работает в фоне (окно скрыто командой <Hide/>).