Реверс-инижиниринг инсулиновой помпы для DIY-терапии
Примерно три года назад я услышал о веб-сайте, предлагающем награду за то, что очень близко моему сердцу: обратная разработка коммуникаций с инсулиновой помпой. Я уже помог создать систему для своей дочери под названием Loop, с помпой Medtronic, для который я осуществил реверс-инжиниринг коммуникаций (бóльшую часть основного протокола связи Medtronic декодировал Бен Уэст с помощью устройства Carelink USB, а я выяснил радиочастоты и провёл некоторую дополнительную работу над протоколом). Но помпу Medtronic требовалось отключать во время гимнастики на несколько часов. Бескамерная конструкция этой помпы Omnipod показалась мне интересной, и у меня были все инструменты для работы.
Система Omnipod состоит из небольшой одноразовой помпы, которая называется модулем (pod), и блока управления (PDM).
Поскольку PDM взаимодействует с модулем по радио, а у модуля нет встроенного интерфейса, это значит, что он полностью управляется по радио. Появляется возможность полной интеграции с Loop, используя только RileyLink или его модифицированную версию.
Джеймс Веддинг назначил вознаграждение, и оно привлекло много внимания, а затем и нужных людей, которые помогли в работе.
Программно-определяемое радио
SDR — потрясающий инструмент. Он делает видимым скрытый мир радио. Есть самые разные виды сообщений, которые постоянно проносятся в эфире, а эти инструменты позволяют вам ковыряться, просматривать сообщения, а после некоторой работы — декодировать маленькие вспышки, которые вы там видите. Если вы ищете сообщения с определённого устройства, вам нужно знать, в какой области начать поиск. Вот где пригодятся публичные документы FCC.
В документации FCC для PDM, RBV-019, сказано, что устройство передаёт в диапазоне 433 МГц. После настройки программного обеспечения SDR на прослушивание в диапазоне 433 МГц при выдаче статуса из PDM появляются такие сигналы:
Как я в конце концов узнал, эти две яркие линии указывают на определённый тип модуляции, называемый частотной манипуляцией, или FSK. Это означает, что частота сигнала изменяется в зависимости от передаваемой информации. Бит 1 отправляется как более высокая частота (верхняя строка), а 0 отправляется с немного более низкой частотой (нижняя строка). С помощью инструмента inspectrum мы можем проанализировать данные, чтобы более чётко распознать 1 и 0. Вот сильно увеличенный вид первого сообщения:
Я написал питоновский скрипт для извлечения этих битов, чтобы мы могли смотреть на них как на целые пакеты.
Оказывается, этот повторяющийся паттерн — часть преамбулы. Чтобы сохранить энергию, приёмники часто уходят в спящий режим и периодически просыпаются, чтобы проверить сигнал. Передатчик посылает преамбулу достаточно долго, чтобы приёмник поймал её в течение одного из коротких периодов прослушивания. Когда приёмник слышит преамбулу, он просыпается, пока не появятся реальные данные.
Нужно пройти через ещё один слой, прежде чем получить фактические пакетные данные. Вы не можете отправлять свои данные по радио точно так же, как исходные биты, потому что приёмник использует переходы для синхронизации во времени, когда ожидать следующий бит. Если у вас есть длинный набор нулей или единиц, то приёмник может выйти из синхронизации. Поэтому радиосвязь обычно использует кодировку, чтобы убедиться в достаточном количестве переходов. В коммуникациях Omnipod используется так называемое манчестерское кодирование. Каждый бит кодируется двумя битами. 1 кодируется как 10, а 0 кодируется как 01.
Всё это потребовалось долго выяснять, и на канале openomni в Slack было много теорий, поскольку мы пытались повторить исходные биты. Марк Брайтон, Дэн Кэрон и @larsonlr добились некоторого успеха с использованием RFCat и Ti Stick для захвата пакетов. Эварист Куржо в конце концов написал инструмент под названием rtlomni, он использует USB-приёмник rtl-sdr для прослушивания пакетов и их декодирования, что оказалось очень удобным и более надёжным, чем методы на основе TI Stick.
Декодирование пакетов
Получив фактические биты, мы начали изучать структуру пакета. Основываясь на том, какие биты менялись между разными модулями и разными командами, мы составили структуру, которая выглядит так:
CRC8
Радио — далеко не идеальная среда передачи. Есть много различных источников помех, которые заставляют приёмник слышать 1, когда отправляется 0, и наоборот. Важно знать, когда это произошло, поэтому большинство протоколов используют контрольную сумму, часто называемую CRC. Приёмник вычисляет CRC по мере получения данных, а последний байт пакета включает CRC, вычисленный передатчиком. Если они не совпадают, приёмник выбрасывает пакет и ждёт повторной передачи.
Протокол Omnipod использовал стандартный 8-битный CRC. Когда мы нашли его, то решили, что очень близки к тому, чтобы понять сообщения. Как мало мы знали…
Сообщения, CRC16
Некоторые сообщения слишком велики, чтобы поместиться в один пакет, поэтому разбиваются на несколько пакетов. Мы начали собирать по кусочкам формат сообщений и заметили ещё один набор битов в конце каждого сообщения, который выглядел как 16-битный CRC. Но он был странный: 5 из 16 бит никогда не устанавливались. Мы испробовали много разных методов, чтобы выяснить, как это кодируется, но ничего не получалось.
Это стало первой большой проблемой: мы могли бы продолжить работу над другими битами в сообщениях, но это мало поможет понять, что отправляется, и мы не сможем сами генерировать новые пакеты, поэтому прогресс замедлился.
Практически безрезультатно прошло несколько месяцев. Наконец, зимой 2016 года член группы под ником @lorelai сообщила, что она успешно скопировала прошивку с более крупного ARM-чипа на PDM и начала утомительный процесс дизассемблирования: принимая инструкции CPU и превращая их в понятный человеку код с семантическими переменными и названиями функций. Она проделала удивительную работу, выяснив различные методы, которые использовались для радиопередачи данных.
Я смотрел на одну из подпрограмм без названия и заметил, что она похожа на стандартную реализацию вычисления CRC по таблице. И в таблице были значения для стандартного 16-битного CRC. Я написал собственную реализацию на таблицах, и она проверялась как обычный CRC. Затем я внимательно посмотрел на то, как написана функция. Нормальная реализация CRC выглядит так:
while (len--) {
crc = (crc << 8) ^ crctable[((crc >> 8) ^ *c++)];
}
Их реализация выглядела так::
while (len--) {
crc = (crc >> 8) ^ crctable[((crc >> 8) ^ *c++)];
}
Заметили разницу? То, что должно было быть побитовым оператором сдвига влево, каким-то образом было закодировано как сдвиг вправо. Это ошибка; нет причин калечить собственный алгоритм CRC, так как это затрудняет определение повреждённых сообщений.
Мы снова вернулись в строй! И возобновили работу по декодированию сообщений, записи сеансов из PDM по доставке болюсов [лекарства], временных баз [Temporary Basal Rate определяет увеличение или уменьшение подачи инсулина — прим. пер.], приостановок подачи и т. д…
Однократно используемое число
У всех команд по доставке инсулина был 4-байтовый фрагмент данных в начале сообщения, который выглядел словно какая-то форма криптографии. Опять же, мы пробовали много разных способов его интерпретации и анализа в контексте сообщений, в которых он был отправлен, но это не был CRC (иногда мы видели одинаковые 4 байта даже в разных сообщениях). И иногда мы видели, что картина повторяется. Это выглядело, возможно, как часть протокола для предотвращения воспроизведения данных. В других протоколах подобная функция называется nonce (однократно используемое число).
Одним из вариантов, который мы рассматривали, была запись базы сообщений для воспроизведения заданных команд. Даже если бы адрес каждого модуля отличался, теперь мы знали, как генерировать CRC, так что могли бы взять прежнюю копию команды, поместить новый адрес на сообщение и пересчитать CRC. Только этот nonce мешал нам использовать данную стратегию. Независимо от команды, модуль принимал только следующий nonce в последовательности, и мы не знали, как генерировать следующий nonce.
Но! У нас ведь есть декомпилированная прошивка PDM, мы можем просто посмотреть там! Итак, мы изучили прошивку PDM, отследили генерацию сообщений в коде и нашли, где должны быть эти четыре байта. Но вместо метода, вычисляющего некоторые криптографические nonce, мы просто нашли четыре символа INS.
. Что за ерунда?!?! Хорошо, каким-то образом эта область сообщения должна обновляться позже в конвейере.
На PDM был еще один чип, ближе к радио. Это был тот же чип, который использовался в модулях, с идентификатором SC9S08ER48, который не был задокументирован в интернете. Вероятно, его изготовили на заказ для Insulet. Может, нам удастся снять прошивку с этого чипа. К сожалению, чип был заблокирован, что предотвратило копирование прошивки.
Работа опять замедлилась… это было похоже на настоящий тупик. Мы направили все умственные усилия на этот nonce, и у нас не было никаких хороших зацепок по математике, стоявшей за ним. И микросхема ER48, которая (возможно) хранила секреты, была заблокирована, и трудно найти какую-то общедоступную информацию, которая помогла бы его взломать.
Рентгеновские снимки
Пытаясь понять ER48, некоторые члены сообщества в Slack предложили сделать рентгеновские снимки. Это было действительно круто, но, к сожалению, не открыло никаких новых возможностей.
Общий снимок
Детализированный снимок
Вскрытие и съёмка
Дэн Кэрон решил обратиться к исследователю, д-ру Сергею Скоробогатову из Кембриджского университета в Великобритании. Дэн читал, что у него есть опыт извлечения кода из заблокированных чипов, и убедил его взглянуть на нашу проблему. Д-р Скоробогатов проводил исследования в области использования SEM (сканирующий электронный микроскоп) для реверс-инжиниринга микросхем. Он предположил, что это возможно, но обойдётся дорого, потребует дорогостоящего оборудования и не гарантирует результат. Джо Моран, который недавно начал использовать Loop после того, как мы встретились на хакатоне Nightscout осенью 2016 года, согласился помочь с проектом. Он договорился с компанией из Кремниевой долины, Nanolab Technologies, на вскрытие и фотосъёмку чипов, а также любезно финансировал работу Nanolab и доктора Скоробогатова (а также его личные модули).
Д-р Скоробогатов попросил Nanolab применить различные методы визуализации, чтобы выяснить, можно ли взломать защиту известными неинвазивными или полуинвазивными методами. В результате появилось много изображений, некоторые из них очень красивые. Это оптические микроскопические снимки кремниевой матрицы.
Общий вид микросхемы под оптическим микроскопом
Общий вид микросхемы под оптическим микроскопом
Были также сделаны снимки конкретных областей матрицы с помощью сканирующего электронного микроскопа. C различными напряжениями тока, различной подготовкой поверхности и различным оборудованием.
SEM-изображение ячеек флэш-памяти. Не показывает данных
К сожалению, ни одно из этих изображений не показало фактическое содержимое флэш-памяти.
У д-ра Скоробогатова был один последний метод, который можно использовать только в случае неудачи. Это был запатентованный метод, на использование которого нужно было получить разрешение университета. Д-р Скоробогатов сделал первоначальный тест и подтвердил, что он способен прочитать данные на этом чипе. Но прежде чем продолжить, нужно было подписать NDA, и поэтому были проведены переговоры о том, кто получит содержимое извлечённой прошивки.
В конечном итоге NDA подписал фонд Nightscout, он взял на себя ответственность за предотвращение несанкционированного раскрытия методов и результатов извлечения памяти.
Результатом этого соглашения и работы стала невероятная статья, написанная доктором Сергеем Скоробогатовым, а также код прошивки. С первого раза в коде оказалось довольно много ошибок, но этого было достаточно для начала работы. На весеннем хакатоне Nightscout Джо обратился к ребятам, не хочет ли кто-нибудь заняться дизассемблированием. Никто не поднял руки. Превращение инструкций процессора во что-то понятное — кропотливая работа, и очень мало людей умеют это сделать. Я попытался покопаться в ассемблере, используя документацию CPU, но очень малого добился и разочаровался. Другие оптимистично просили код прошивки с ожиданиями быстрого прогресса, затем осознавали масштаб и сложность задачи — и тихо отваливались.
Пример дизассемблирования инструкций SC908
Оказывается, у Джо тоже был обширный опыт работы с ассемблером, и он начал сам выполнять эту тяжёлую задачу. В июле д-р Скоробогатов завершил вторую операцию по извлечению памяти с гораздо меньшим количеством ошибок. В течение лета Джо Моран неустанно работал над отображением огромного количества инструкций процессора и их постепенным объединением в общую картину псевдокода модуля.
В конце концов, к нам присоединился Кен Ширрифф, эксперт по аппаратному реверс-инженерингу, и он значительно ускорил процесс. Вместе Джо и Кен в итоге транслировали достаточно кода, чтобы найти функцию, которая кодирует nonce. Это случилось в сентябре 2017 года.
RileyLink и Loop
Мы обновили питоновские скрипты openomni, но теперь пришло время сосредоточиться на RileyLink + iOS, поэтому я начал работать над OmniKit и обновлениями прошивки для RileyLink. Я верил, что у нас есть основы протокола, а остальное — просто детали. Опять же, совершенно недооценивая, сколько ещё впереди.
Мне пришлось написать новую прошивку, которая обрабатывает модуляцию и кодирование модуля. Мне также пришлось переписать то, как два чипа на RL разговаривают друг с другом, чтобы обрабатывать нули, поскольку в Medtronic нули были специальным концом маркера пакета. Многое в Loop пришлось переработать для поддержки нескольких помп, а также сделать новые интерфейсы для поддержки сопряжения, деактивации и обработки ошибок. К счастью, Нейт Рэклифт заложил прочный фундамент в Loop, чтобы всё это стало возможным.
Между тем, продолжалась работа по пониманию формата команд. Всё тщательно документировалось в вики openomni, наиболее полной документации по протоколу. Джо, Эварист и Эльке Ягер с течением времени проделали действительно огромную работу по расшифровке сообщений и обновлению страниц. Различные члены канала Slack внесли свой вклад по захватам пакетов PDM и модуля, чтобы помочь общим усилиям по декодированию.
Декодирование было забавной работой, с большим количеством небольших побед, поскольку каждый компонент каждой команды расшифровывается, и мне очень понравилось работать над этой частью, постепенно добавляя код в Loop. В апреле 2018 года я поделился в Slack, что сделал «спаренную через iPhone + RL первичную каннуляцию по запрограммированному базальному расписанию, а затем заболюсил 5 юнитов».
Прошивку RL 2.0 завершили в июле 2018 года, и новые поставки пошли уже с ней. Была надежда, что эти платы можно будет использовать с Loop и Omnipod, но существующая антенна 915 МГц оказалась слишком плохой, чтобы эффективно работать на частоте 433 МГц.
Декодирование и реализация значительно продвинулись за лето, и Loop постепенно приближался к работоспособности. Джо сделал удивительную вещь, предоставив мне финансирование, чтобы я бросил свою дневную работу и сосредоточился на этом проекте, и в конечном итоге я присоединился к замечательной команде Tidepool. Конечно, в области DIY и законодательного регулирования медицинской техники происходило больше событий, которые я не буду освещать, но это было очень интересное лето!
Крикуны
Когда в драйвере появилось больше функций, я подключил его к Loop, включив возможность автоматической настройки доставки по времени. На этом этапе довольно часто получались «кричащие» модули, когда некоторые из внутренних проверок модуля обнаруживали проблему, и он прекращал доставку инсулина.
Но это казалось решаемой проблемой, так как мы продолжали находить небольшие расхождения в пакетах Loop и оригинального PDM при ручной отправке команд, и я предположил, что если мы исправим их все, то «крики» прекратятся.
Рабочий Loop!
3 октября 2018 года Джо надел на себя управляемый модуль под управлением Loop и стал первым пользователем Loop Omnipod, хотя он не сказал мне сразу, поскольку знал, что я буду беспокоиться. Когда он сказал мне, я всё еще волновался. Мы видели, как работает модуль, и понимали функциональность, и основной алгоритм был проверен в течение длительного времени, но всё же…
Месяц спустя, на хакатоне Nightscout в ноябре 2018 года, ещё несколько авантюристов решили попробовать его на себе, а также стали частью небольшой закрытой группы тестирования, которая вырастет до более чем 30 человек, прежде чем код будет опубликован.
К сожалению, у нас всё ещё встречались «крики» модуля, часто происходящие до завершения полных трёх дней использования, и мы тщательно сравнивали команды Loop с образцами от PDM. В этом процессе был особенно полезен Эльке: он написал скрипт для автоматической проверки команд с оригинальными версиями. Я начал беспокоиться, что неустойчивую работу модулей вызывают возросшие требования к батарее для связи каждые пять минут.
Отводы регулятора напряжения питания в модуле, просверленные через пластмассу задней панели, на суперклее
Поэтому я начал измерять напряжение питания модуля с помощью Arduino, записывать данные и сохранять их в локальной базе данных для визуализации. Я сравнивал PDM и Loop.
Долговременное изменение напряжения питания модуля
К сожалению, это также оказалось тупиком; используя PDM и вводя большое количество инсулина, я мог довести модуль до более низкого напряжения, чем за всё время жизни модуля Loop, и не смог заставить модуль «кричать». Казалось, что напряжение не проблема, должно быть что-то ещё.
RileyLinks с 955 МГц (слева) и катушечные антенны 433 МГц (справа)
В какой-то момент я заметил, что если обмен сообщениями с модулем не удался, то модуль иногда продолжал попытки завершить обмен, повторно отправляя пакеты снова и снова. Логи тестеров тоже показывали много сбоев, поэтому я начал экспериментировать с антеннами. Обе проблемы должны быть связаны с качеством связи. Я планировал попробовать разные антенны и заказывал их в разных местах в интернете, но у меня не было времени их тестировать, пока это не стало приоритетом.
У меня было несколько гибких антенн 433 МГц, которые можно прикрепить к внутренней стороне корпуса RL. Они часто демонстрируют лучшую производительность в некоторых сценариях, но не в других; слишком ненадёжно. Когда я добрался до катушки, она показывала хорошую производительность очень последовательно и на очень удивительных диапазонах. Время делать новый корпус для RileyLink.
С новой антенной и некоторыми оптимизациями, которые уменьшили обмен сообщениями, всё ещё выполняя корректировки каждые 5 минут, крики стали очень редкими. Вероятно, сопоставимо с обычным использованием модулей с PDM. За последние 7500 часов испытаний в реальном времени 94% модулей завершили работу без сбоев.
Тестирование и документация
Группа тестирования медленно росла: к системе постоянно присоединялись новые пользователи, которые свежим взглядом могли оценить, какие части выглядят запутанными. Эти тестеры мирились с большим количеством кричащих модулей и внесли очень большой вклад в улучшение работы Loop с Omnipod. В основном они присылали отчёты о проблемах и логи работы.
В этих отчётах есть лог сообщений, которые можно проанализировать с помощью инструмента, сделанного Эльке. Он даёт представление, если у нас попались какие-то искажённые команды, а также позволяет собрать статистику по определённых частям взаимодействия Loop с модулями.
Марион Баркер присоединилась к группе тестирования и добавила специальную отчётность и дополнительную статистику по прогрессу тестирования — и мы смогли использовать её статистику успешных модулей против сбоев, чтобы иметь представление о прогрессе на высоком уровне.
В конце концов, к группе тестирования присоединилась Кэти ДиСимоне. Она начала большую реструктуризацию loopdocs.org с документацией по использованию Loop с несколькими устройствами. Ожидание версии Loop, которая работала с Omnipod, было невероятно высоким, и без хорошей документации было ясно, что нас завалят одними и теми же вопросами.
Новые функции Loop
Интеграция с Omnipod потребовала переосмысления некоторых элементов интерфейса и добавления новых элементов управления. Модуль не сообщает о батарее, и пользователь мало что может сделать с низким зарядом, если это произойдёт, поэтому отображение виджета уровня заряда батареи не имеет смысла. Кроме того, без пользовательского интерфейса на помпе, пользователь должен иметь возможность быстро отменить болюс. Значок резервуара изображал резервуар Medtronic, поэтому мы хотели переделать его. Спасибо Полу Форджионе за разработку логотипа модуля, который теперь показывает уровень резервуара.
Благодарности
Спасибо всем людям, которые помогли пройти этот долгий путь, чтобы мы реализовали цель, которую поставили перед собой давным-давно. Я знаю, что упомянул не всех причастных и не все события. Это невозможно в одной статье, и у меня есть только личный опыт. Трудно представить, сколько часов на это ушло. Если сложить их все, я уверен, получится шокирующая цифра. Не говоря уже о работе по созданию самого Omnipod, которая, как мне кажется, затмевает все эти усилия. Так что спасибо вам всем. Кроме того, многие из этих часов в противном случае были бы проведены с семьями. Я очень ценю понимание своей жены и детей из-за того времени, которое я потратил на это, и хочу поблагодарить их тоже.
Примечания
Я должен упомянуть Йоакима Орнштедта как одного из участников декодирования openomni, а также создателя, вероятно, первой интеграции с omnipod. Он построил устройство, которое использовало оптическое распознавание символов (OCR) для извлечения данных из PDM, и подключил цифровые кнопки к физическому PDM через другой микроконтроллер. Такой подход трудно масштабировать, но он очень умный и обходит многие проблемы, с которыми нам пришлось иметь дело с решением на базе RE. Я действительно восхищён тем, как он справился с проблемой и наладил работу за крошечную долю того времени, которое потребовалось, чтобы заставить устройство работать с Loop.
Автор: m1rko