Большинство устройств для mesh-сетей в своей основе представляют собой всего лишь микроконтроллер, подключённый к LoRa-чипу компании Semtech (подробнее об этом можно прочитать в моей предыдущей статье). Конечно, некоторые производители добавляют GPS-модуль, клавиатуру и экран, но основные компоненты, по сути, эти два кирпичика Lego. Микроконтроллер отправляет LoRa-чипу команды вроде «передай эти байты на частоте 910 МГц», а тот их выполняет. Когда LoRa-чип принимает сообщение, он генерирует прерывание для микроконтроллера, сообщая: «Я получил твои байты. Забирай».
Чтобы превратить такую связку в узел mesh-сети, достаточно написать для микроконтроллера код, определяющий формат кодирования пакетов, правила их пересылки и способ взаимодействия с клиентским приложением по Bluetooth. Попросите друга установить ту же прошивку, и вуаля: у вас есть собственная mesh-сеть.
Именно по такой схеме я создал свою плату на базе LR2021. Если вы регулярно читаете мои статьи, то пока ничего нового я вам не рассказал.
Но вот что действительно интересно: я выяснил, как избавиться от микроконтроллера.
Ну, в некотором смысле. Впрочем, я забегаю вперёд.
Тайная жизнь LR2021
У самого нового и совершенного LoRa-чипа компании Semtech LR2021 есть одна секретная особенность. Помимо того, что это радиотракт в одном чипе, внутри него также имеется микроконтроллер. В руководстве об этом почти ничего не говорится. Он нужен для обработки команд, поступающих по шине SPI, и выполнения некоторых вспомогательных операций внутри чипа, но в основном он не вмешивается в работу системы. Предполагается, что пользователь не будет программировать его так же, как nRF52 или ESP32, на котором работает его mesh-узел. Нигде в технической документации вы не найдёте упоминания о том, что это 32-мегагерцевый процессор Synopsys ARCv2 EM, что у него 16 регистров общего назначения и что инструкция умножения 32-битных чисел (mpy) выполняется за три такта. Пользователю всё это знать совершенно необязательно.
Обычно меня бы это тоже не интересовало, если бы в документации не описывалась функция под названием «patch RAM», или сокращённо PRAM. Это 4 КБ памяти, в которую, согласно документации, перед использованием радиотракта нужно загружать патчи, предоставляемые Semtech. Сам патч представляет собой непрозрачный двоичный блоб данных, который передаётся по шине SPI вместе с командой «загрузить эти байты в PRAM». Именно так эту память и предполагается использовать: для загрузки непрозрачных программ Semtech, внутреннее устройство которых пользователю знать не нужно.
Любой, у кого есть хотя бы небольшие хакерские задатки, увидев эту фичу, задастся вопросом: «Хм, а нельзя ли в эту область памяти загружать собственные программы?». За этим обычно сразу следует: «Конечно, можно, но реверс-инжиниринг требует кучи усилий, а мне за это не платят».
Но мы живём в эпоху ИИ: сегодня каждый может всего за 20 долларов в месяц призвать себе на помощь высококлассного хакера. Итак, я подключил радио, чтобы Claude занялся радиолюбительством. Как ни удивительно, но это не вызвало срабатывания ни одной из защитных систем, которые создатели Claude постоянно добавляют против реверс-инжиниринга. Примерно за 20 минут у меня был готов полный отчёт о процессоре и принципах работы этой области памяти. Но, возможно, ИИ просто галлюцинирует?
Нет, не галлюцинирует.
Используем LR2021 в качестве SDR, часть вторая
Ранее я упоминал хак SAMS0N1TE SDR, с которого всё это и началось. Исследователи обнаружили недокументированный опкод, позволяющий считывать необработанные I/Q-сэмплы непосредственно с LR2021 и тем самым превращать его в универсальную программно-определяемую радиосистему (SDR). Его можно подключить к компьютеру и управлять им через SoapySDR. Более того, с его помощью можно даже передавать радиосигнал. Для настолько дешёвого радиоустройства это впечатляющее достижение.
Работает это следующим образом: мы отправляем LR2021 секретный опкод, после чего он начинает записывать необработанные I/Q-сэмплы в буфер. Затем микроконтроллер должен выполнить некоторый код, который как можно быстрее извлечёт эти данные из буфера LR2021 и передаст их через USB-порт. В репозитории разработчиков можно увидеть замечательные результаты.
У меня не было такого же микроконтроллера, как у SAMS0N1TE, поэтому мне пришлось реализовать всё это заново для nRF52. Но как только I/Q-сэмплы оказались на моём компьютере, я смог построить спектрограмму преамбулы LoRa.

Отлично!
Но у меня всё равно оставался вопрос: а нельзя ли проделать всё это непосредственно на LR2021, загрузив собственный патч PRAM и перехватив управление его процессором?
Оказывается, можно.
На основании отчёта Claude о PRAM можно написать программу на C, выполняющую быстрое преобразование Фурье (БПФ, FFT), и скомпилировать её в машинный код, понятный LR2021. Теперь мне больше не нужно выгружать необработанные I/Q-сэмплы из LR2021, а можно сразу считывать уже обработанные данные.
Ниже представлен водопадный спектр, полученный с помощью этого метода.

Вертикальная линия в верхней части водопадного спектра — это несущая частота 908,03 МГц, а ниже видны несколько пакетов LoRa. И то и другое передаётся другим устройством на базе LR2021.
Если присмотреться, можно заметить, что обработка происходит со скоростью 6,6 кадра в секунду: каждая строка водопадного спектра соответствует одному кадру. Да, это впечатляющая демонстрация, но с точки зрения радиочастотных систем 6,6 кадра в секунду — крайне низкая скорость.
Можно ли ускорить обработку? Да.
Вот тот же процесс, но уже со скоростью 462 кадра в секунду.

Ускорение в 70 раз? Что ж, отличная работа, Claude. Заслуживаешь пятиминутного перерыва.
Стоит также рассказать, что именно позволило добиться такого ускорения, чтобы получить представление о возможностях секретного процессора LR2021.
Во-первых, LR2021 умеет записывать необработанные I/Q-сэмплы в память, пока процессор занимается другими задачами. Эта возможность называется прямым доступом к памяти (Direct Memory Access, DMA). Она позволяет некоторым периферийным устройствам внутри чипа самостоятельно записывать данные в память и перемещать их между его областями, не задействуя центральный процессор. В данном случае DMA используется для передачи данных из аналого-цифрового преобразователя (АЦП) LR2021 в оперативную память. Благодаря этому процессор может выполнять БПФ для предыдущей порции данных, пока поступают новые cэмплы. Альтернативой было бы считывание данных непосредственно из АЦП самим процессором с последующей записью значений в память. Разумеется, такой способ был бы гораздо медленнее.
Во-вторых, для всего этого требуется память, причём больше, чем доступно в PRAM. PRAM — единственная область, в которой разрешено хранить исполняемый код, помимо прошитого на производстве ROM. Однако после внимательного изучения других областей памяти обнаружились места, где можно безопасно хранить данные. Для этого я записывал в эти области случайные данные и проверял, изменяются ли они впоследствии. Если нет, можно с некоторой уверенностью считать, что эти области не используются. Здесь мы можем хранить, например, таблицы предварительно вычисленных значений и рабочую область для операций БПФ.
И это лишь небольшая часть возможностей. Если вам любопытно, можете изучить подробности
Допустим, но что насчёт mesh-узла?
Благодарю за то, что выслушали мой экскурс на тему SDR. Теперь вернёмся к тому, о чём я говорил в начале: mesh-узлу не нужен микроконтроллер… ну, или вроде того.
Возможно, вы уже догадались, чем я занялся дальше. Я портировал прошивку ретранслятора MeshCore в патч PRAM. Именно так: теперь ретранслятор может работать целиком на LR2021. Строго говоря, микроконтроллер всё ещё нужен, чтобы загрузить программу в PRAM, но после его услуги больше не требуются. Он переходит в спящий режим, а LR2021 берёт всё остальное на себя.
Ниже видно, как моё устройство MeshCore Companion принимает ретранслированный сигнал от ретранслятора LR2021-rpt, полностью работающего на LR2021.

Поддерживаются direct и flood routing, а также трассировочные пакеты и анонсирование. В ретрансляторе есть очередь передачи, в которой могут ожидать отправки до четырёх сообщений максимального размера, и таблица дедупликации на 64 записи, благодаря которой каждый пакет передаётся только один раз. В нём даже есть таймер отправки сообщений анонсирования, работающий на основе 32-килогерцевого счётчика, расположенного по адресу памяти 0xF11014.
Разумеется, пришлось пойти на некоторые компромиссы. Я не стал портировать на LR2021 операции шифрования, поэтому его сообщение анонсирования заранее подписано и загружено в память. Да, это означает, что его временная метка никогда не меняется.
Для реализации всего этого потребовались совершенно безумные хаки. Во-первых, если загрузить в PRAM программу, которая полностью перехватывает управление процессором и не возвращает его, начинаются всевозможные сбои. Процессор внезапно оказывается не в состоянии выполнять всю необходимую служебную работу, чтобы система продолжала функционировать. Поэтому пользовательский код ретранслятора должен взаимодействовать со встроенной прошивкой. Для этого он перехватывает основной цикл обработки событий ROM. В ходе обратной разработки Claude обнаружил, что указатель на функцию по адресу gp+0x34 позволяет перехватывать каждое событие, что можно использовать для совместной многозадачной работы со встроенной прошивкой. Если событие относится к функциям ретранслятора, код может перехватить его обработку; в противном случае он может просто передать событие встроенной прошивке.
Во-вторых, выяснилось, что собственный процессор LR2021 не может самостоятельно инициировать передачу в полном объёме. Дело в том, что чип рассчитан на получение всех передаваемых байтов через шину SPI. Это ограничение заложено непосредственно в аппаратную часть. Нельзя просто записать байты в буфер передачи и запустить передачу из процессора LR2021. Это обязательно должна делать сама шина SPI. Для ретранслятора это серьёзная проблема.
При первой попытке решить её я попробовал вручную модулировать пакеты LoRa, напрямую управляя передатчиком чипа. Это сработало, но получилось очень топорно. Да это же, чёрт возьми, чипа для LoRa! Дайте мне использовать его оборудование LoRa.
При второй попытке я решил воспользоваться недокументированным коммутатором (мультиплексором) внутри чипа и проверить, можно ли заставить его самостоятельно отправлять команды по SPI. Идея заключалась в следующем: этот мультиплексор работает примерно как коммутатор, позволяющий соединять различные линии передачи данных друг с другом. Может быть, удастся подключить одну из линий ввода-вывода общего назначения к шине SPI и таким образом отправлять сообщения? Увы, это не сработало.
А вот то, что в итоге заработало, до сих пор вызывает у меня улыбку: оказалось, что чип может отправлять сообщения самому себе, если включать и выключать внутренние подтягивающие резисторы к питанию и земле на линиях SPI. Боже мой!
Здесь стоит дать некоторые пояснения. Принцип работы SPI заключается в управлении напряжением на нескольких линиях передачи данных: оно устанавливается на высокий или низкий уровень. Напряжение 3,3 В обычно соответствует логической единице, а 0 В — логическому нулю. Хотя протокол SPI не предусматривает использования таких механизмов, многие микроконтроллеры оснащены встроенными подтягивающими резисторами к питанию и к земле. Они нужны, чтобы поддерживать на линиях определённый уровень напряжения, не позволяя ему произвольно колебаться. В данном случае мы можем использовать эту особенность не по назначению и генерировать сигнал SPI, включая встроенные подтягивающие резисторы к питанию и к земле на линиях передачи данных SPI. Если нужно передать 1, чип включает подтяжку к питанию. Если 0 — подтяжку к земле. Последовательно переключая их таким образом, он может передать самому себе целое сообщение. Невероятно! Я не мог поверить, что Claude удалось заставить это работать.
Это лишь самое интересное, но если вы хотите узнать больше, в репозитории есть масса дополнительных технических подробностей.
И, конечно же, я не мог обойти стороной Meshtastic. У меня также есть блоб PRAM, который можно загрузить, чтобы запустить на LR2021 узел типа Client. Как и в случае с блобом для MeshCore, здесь пришлось пойти на компромиссы. Поскольку шифрование не реализовано, узел не может расшифровывать получаемые сообщения. Кроме того, в нём не реализована поддержка Bluetooth.
И это ещё не всё
Хотите вывести граффити прямо на водопадном спектре? Почему бы и нет! У меня и для этого есть программа PRAM. Ниже показано, как она передаёт тональные сигналы таким образом, что на водопадном спектре RTL-SDR появляется надпись AB Labs LLC.

Кроме того, можно запускать процедуры демодуляции непосредственно в PRAM, добавляя в чип поддержку модемов, которая изначально отсутствует. А поскольку можно также перехватить управление внутренней фазовой автоподстройкой частоты (ФАПЧ, PLL) и напрямую передавать тональные сигналы, таким же способом можно добавить и новые возможности передачи. К сожалению, насколько мне удалось выяснить, поддерживаются только передачи с постоянной огибающей.
Также у меня есть программа для PRAM, позволяющая очень быстро сканировать несколько частот LoRa. Насколько быстро? Одно радиоустройство передавало сигналы на случайно выбранных частотах из 20 возможных, а другое, работавшее с этой программой-сканером, смогло обнаружить 89 из 90 передач.
Думаю, именно в таких задачах взлом PRAM может оказаться действительно полезным. Запускать весь узел mesh-сети непосредственно на LR2021, вероятно, слишком сложно и нецелесообразно, но возможность переложить на него выполнение отдельных функций выглядит гораздо интереснее. Например, в многочастотном ретрансляторе LR2021 мог бы выполнять сканирование частот и будить микроконтроллер только при обнаружении передачи. Или, возможно, LR2021 мог бы предварительно фильтровать принимаемые пакеты, прежде чем будить микроконтроллер. Здесь есть множество возможностей для экспериментов.
Как я этого добился?
Наверно, стоит рассказать и о том, как мне всё это удалось. Я какой-то волшебник? В прошлом для реализации любого из этих хаков потребовался бы труд крайне опытного специалиста, одержимого своим делом. Понадобились бы множество попыток тщательного исследования оборудования и глубокие знания о внутреннем устройстве подобных чипов. И даже если у кого-то хватило интеллектуальных способностей, чтобы во всём этом разобраться, вряд ли у многих найдётся терпение на такую кропотливую работу.
Я профессионально занимаюсь программированием более пятнадцати лет. Я работал в таких компаниях, как Twitter и Stripe, и не буду скрывать, что горжусь этим. Отбор сотрудников в Stripe строже, чем при поступлении в Гарвард. Однако новые ИИ-модели полностью превзошли меня с точки зрения навыков. Я наблюдаю за рассуждениями Claude в процессе реверс-инжиниринга этих чипов, и мне становится очевидно, что я стал свидетелем чего-то сверхчеловеческого. Он быстр, эффективен и как будто знает всё.
Всего год назад работа с Claude походила на работу со стажёром. Да, очень умным новичком, но склонным к совершению дурацких ошибок. Казалось, я вношу свою вклад, направляя его.
Но теперь сложно понять, в чём же конкретно состоит мой вклад. Я подключил к ноутбуку два радио и RTL-SDR, а потом попросил Claude портировать MeshCore на LR2021. И он справился, по сути, не требуя от меня никакой помощи и руководства. Он пыхтел над задачей в течение часа, и сообщил мне о завершении. Когда я рассказываю об этом, мне кажется необходимым отдать должное Claude, словно фраза «это сделал я» — своего рода моральное прегрешение, попытка присвоить себе заслуги, которые в значительной степени принадлежат ему самому.
Я стараюсь, чтобы тон моих постов оставался непринуждённым, но, если честно, испытываю сильно противоречивые чувства. С одной стороны, превращение таких навыков в общедоступный и доступный по цене ресурс может принести обществу немало пользы. С другой стороны, последние несколько лет я в основном наблюдаю за тем, как обесценивается мой труд, а машинный интеллект превосходит меня во всех профессиональных аспектах. Сразу уточню: компании по-прежнему нанимают сотрудников, но как никогда прежде сильно ощущение, что мы приближаемся к эндшпилю.
Дополнение: да, на нём можно запустить Doom
Я проснулся этим утром (буквально час назад), и у меня возникла мысль, от которой я никак не могу избавиться: на LR2021 обязательно должен запуститься Doom. В чём вообще смысл всей этой работы, если у меня не получится это сделать? Можно будет просто удалить мой блог и перестать писать.
У LR2021 есть несколько выводов DIO — обычных линий ввода-вывода общего назначения, которыми можно управлять с помощью полностью документированных опкодов. Ничего необычного здесь нет. Первой мыслью было подключить к этим выходам OLED-дисплей. Но, чёрт возьми, это же радиочип! Он должен управлять дисплеем с помощью своего передатчика.
Возможно, решением станет векторная графика? Вы наверняка видели демо, в которых на экране осциллографа рисуют картинки. Один сигнал задаёт значение X, второй — значение Y, а старый добрый осциллограф с люминофорным экраном обеспечивает достаточный гостинг для того, чтобы можно было рисовать изображения при достаточно быстром перемещении яркой точки.

Но как нам это сделать? Как получать значения X и Y из радиосигнала? Если бы LR2021 был настоящим I/Q-передатчиком, можно было бы закодировать одну координату в I, а другую в Q, но, к сожалению, он поддерживает только модуляцию с постоянной огибающей1. Было бы замечательно, если бы это сработало, потому что передатчик управлял бы дисплеем в самом прямом смысле.
1 Любопытно, что LR2021 поддерживает OOK, при котором огибающая сигнала не является постоянной. Разобраться в принципе его работы, вероятно, было бы весьма перспективно. Подозреваю, что амплитуда модулируется за счёт управления усилителем мощности.
Можно было бы просто воспользоваться встроенным режимом FLRC, работающим на скорости до 2,6 Мбит/с. Он мог бы передавать готовые кадры целиком (например, в виде растровых изображений), а компьютер отображал бы их на экране. Но меня такой вариант не устраивал. При таком подходе передатчик как будто не управляет дисплеем. Формально это всё ещё так, в некотором смысле, но ощущения совсем не те. Я здесь художник, а это мой холст, и мириться с таким решением я не собираюсь.
В итоге я остановился на схеме с FSK. Различные отклонения частоты от несущей соответствуют разным значениям. Сначала я передаю координату X, затем координату Y. На принимающей стороне по-прежнему нужен демодулятор, но именно эти координаты X и Y управляют перемещением яркой точки на векторном дисплее. Ниже можно увидеть визуализацию этого процесса. На ней показана реальная осциллограмма сигнала, переданного с LR2021 и записанного с помощью RTL-SDR.

Здесь стоит признать, что векторного дисплея у меня на самом деле нет, поэтому вторую сторону этой системы придётся эмулировать программно. Да-да, я знаю. Но всем художникам приходится идти на компромиссы.
В итоге система будет работать следующим образом. LR2021 будет выполнять все вычисления для трассировки лучей на своём секретном встроенном процессоре при помощи специального блоба PRAM. Этот блоб будет формировать FSK-сигнал, который примет RTL-SDR. RTL-SDR извлечёт из FSK-сигнала значения координат X и Y и будет перемещать яркую точку по имитируемому люминофорному экрану. Компьютер, подключённый к плате с LR2021, будет передавать нажатия клавиш с клавиатуры, позволяя перемещаться по карте.
Всё состояние игры будет храниться на LR2021, а отрисованные кадры вообще не будут проходить через шину SPI, только через передатчик.
Ты всё понял, Claude? Перерыв окончен. Пора браться за дело!

Без лишних предисловий покажу вам версию Doom для LR2021. Внимание: в видео присутствует мерцание.
А вот статические кадры:

Вот и всё, мы прошли финального босса взлома LR2021.
Но так ли это? Правильнее было бы сказать «Да, он выполняет рейкастинг». В игре нет ни орд демонов, ни оружия. Это я оставлю читателям в качестве упражнения.
Подробности см. в репозитории.
Автор: interpres


