Разлочиваем модем Quectel EG25-G в робособаке Unitree Go2
Я Лев Ларин, инженер Unibot. Мы подключаем роботов к UNIBOT Fleet и дорабатываем их управление, интеграции и связь.
В первом разборе мы сняли региональное ограничение с Quectel EG25-GC в роботе-официанте. Во втором разобрались с RG255C-GL в Agibot X2 Ultra. А в статье про Unitree Go2 получили root, исправили Wi-Fi и добавили русские голосовые команды.
Теперь очередь дошла до сотовой связи этой собачки. В нашем Go2 установлен Quectel EG25-G с eSIM. Профиль читается, российская сеть видна, регистрации нет. Идём по знакомому пути: AT-диагностика, копия радиопрошивки, проверка MCC в коде Qualcomm Hexagon, точечное изменение и загрузка обратно.
После перезапуска модем зарегистрировался в Билайне.
Что стоит в Go2
У Go2 есть встроенная сотовая связь с eSIM. На нашем экземпляре активен профиль Orange: MCC/MNC домашней сети — 20801, APN — orange.m2m.spec. В России такой профиль должен регистрироваться в роуминге.
Сам модем подключён по USB к основному Linux-контроллеру робота. В него мы уже могли зайти по SSH.

Изображение модуля EG25-G с сайта Quectel.
Параметр | Наш экземпляр |
|---|---|
Робот | Unitree Go2 |
Модем | Quectel EG25-G |
Прошивка |
|
USB VID:PID |
|
Linux внутри модема | ARMv7, ядро 3.18.44 |
Радиокод | Qualcomm Hexagon, сжатие Q6zip |
По USB ID устройство может определяться как EC25. Модель и полную версию мы уточнили командами модема. Прошивка здесь GB, а в первом роботе была GC: знакомое семейство ещё не означает совпадения образов и адресов.
eSIM читается, сеть не регистрируется
Основной AT-порт и GPS-порт уже использовал штатный vui_service, с модемом также работал ModemManager. Для диагностики выбрали свободный вспомогательный AT-интерфейс.
Начальное состояние:
CPIN: READY
CFUN: 1
CREG: 0,2
CGREG: 0,2
CGATT: 0
QNWINFO: "WCDMA","25020","WCDMA 2100",10563
QENG: "servingcell","LIMSRV",...
Профиль SIM доступен, радио включено, модем видит российскую сеть, но остаётся в ограниченном обслуживании. В ответах CREG и CGREG значение 2 означает поиск регистрации.
Обычная операторская персонализация была выключена: AT+CLCK="PN",2 возвращал 0. Роуминг и выбор технологии стояли в автоматическом режиме. В прочитанной области SIM-файла EF FPLMN, где хранятся запрещённые сети, были только FF.
APN оставили штатным. Следующим шагом проверили радиопрошивку.
Доступ к Linux самого модема
Временно включили USB-интерфейс ADB через механизм QADBKEY. ADB-клиент запустили на контроллере робота — USB-хосте модема.
Здесь ADB нужен для доступа к отдельному Linux внутри EG25-G. На стороне робота тоже Linux; Android в этой цепочке не требуется.
Из /firmware/image скопировали 49 файлов, около 48 МБ. Оригиналы сохранили на компьютере и контроллере робота. Для анализа взяли заводской пакет той же версии и побайтно сверили с ним все modem.*, прочитанные с устройства.
В release notes этого выпуска описаны ограничения регистрации в России и Иране. В modem.b22 нашлись строки:
reg_sim.c:quectel_check_current_plmn_is_need_forbidden MCC =%d%d%d
reg_sim.c:Current PLMN is Russian PLMN ,Forbidden
Оставалось найти функцию, которая ими пользуется.
Где оказался MCC 250
Нужный код был сжат Q6zip и лежал в modem.b19. У этого сегмента нет обычного ELF-флага исполняемого кода. Поиск только по исполняемым сегментам его пропускает.
Распаковали сегмент инструментом qualcomm-q6zip. Параметры нашей сборки:
Параметр | Значение |
|---|---|
Версия Q6zip |
|
Число страниц | 4450 |
Размер распакованной страницы | 4096 байт |
Страниц с двумя словами метаданных | 3922 |
Размер распакованного кода | 18 227 200 байт |
Виртуальная база |
|
Граница метаданных важна: у последних страниц этих двух слов уже нет. Если продолжать их пропускать, распаковка даст неправильный результат. Виртуальную базу подтвердили таблицей отображения в modem.b13, затем разобрали инструкции Hexagon в Rizin.
По адресу 0xd023afc0 нашлась короткая функция. Она извлекала из аргумента три полубайта с цифрами MCC. Упрощённо:
a = packed_mcc & 0xf;
b = (packed_mcc >> 4) & 0xf;
c = (packed_mcc >> 8) & 0xf;
if (a == 2 && b == 5 && c == 0)
return 1;
if (a == 4 && b == 3 && c == 2)
return 1;
return 0;
MCC 250 — Россия, 432 — Иран. Десятичного числа 250 в этом условии нет: компилятор сравнивает отдельные цифры.
Проверили и вызывающий код. Он вызывал эту функцию по адресу 0xd023b030, а при результате 1 переходил в ветвь с сообщением Current PLMN is Russian PLMN ,Forbidden. Так связали сравнения цифр с запретом регистрации.
Один бит в радиокоде
Повторили вариант из первого кейса: заменили среднюю цифру российского MCC с 5 на 7. Условие стало распознавать 270 вместо 250. Проверка 432 осталась прежней.
В распакованном коде изменилось сравнение по адресу 0xd023afd8:
Было: P0 = cmp.eq(R1,#0x5)
Стало: P0 = cmp.eq(R1,#0x7)
Трассировка декодера Q6zip показала, что нужная инструкция записана непосредственным литералом. Изменение удалось внести в сжатый поток без перепаковки всего сегмента.
Заново распаковали изменённую страницу и сравнили с оригиналом: отличается только нужное сравнение. Все остальные сжатые страницы, словари, указатели, метаданные и длина modem.b19 совпали. В радиокоде снова хватило одного бита.
Хеши и подпись
После изменения b19 пересчитали его SHA-256 в таблице modem.b01 и обновили подпись контейнера. В этой сборке:
modem.mdt = modem.b00 + modem.b01
Поэтому записывать предстояло три файла: modem.b19, modem.b01 и modem.mdt.
Здесь было отличие от EG25-GC из первой статьи: исходная цепочка сертификатов принадлежала Quectel. Прежде чем менять её, проверили состояние аппаратной аутентификации на самом модеме:
AT+QCFG="secbootstat"
+QCFG: "secbootstat",0
/sys/class/qtee/secboot_state
auth_disable
Хеш корневого сертификата в secboot_rootca_hash был нулевым. При таком исходном состоянии модем позволял загрузить образ с другой цепочкой.
Исходную QPSA/RSA-подпись проверили по открытому ключу из заводского образа. Для изменённого контейнера создали локальный конечный сертификат под опубликованной тестовой CA Qualcomm. Сохранили аттестационные поля HW_ID, SW_ID, OEM-параметры и даты исходного сертификата. Затем проверили новую подпись, цепочку и все 24 записи таблицы хешей.
Настройки Secure Boot и аппаратные предохранители оставались в исходном состоянии.
Запись и перезапуск
Раздел /firmware был смонтирован read-only. Свободными оставались примерно 7,3 МБ, а modem.b19 занимал 10,6 МБ. Для полного временного дубликата рядом с оригиналом места не хватало.
Сохранили три исходных файла в /cache модема и подготовили откат. В большом b19 изменили один байт на месте. Небольшие b01 и mdt заменили через временные файлы и rename. Проверили SHA-256, выполнили sync и вернули /firmware в read-only.
Перед перезапуском запустили на контроллере отдельный процесс проверки загрузки: при отказе DSP он должен был вернуть оригиналы. Затем перезапустили модем командой AT+CFUN=1,1.
После загрузки получили:
DSP state: ONLINE
crash_count: 0
Все три хеша совпали с подготовленным образом. Откат не потребовался, контроллер робота продолжал работать.
При завершении поправили последствие USB-переподключений: штатный vui_service держал дескрипторы старых AT- и GPS-портов. Его перезапустил штатный supervisor; сервис снова открыл действующие порты. После этого выключили ADB модема и вернули исходный состав USB-интерфейсов.
Регистрация в Билайне
После загрузки исправленной прошивки модем зарегистрировался в сети:
CREG: 0,5
CGREG: 0,5
CGATT: 1
COPS: 0,0,"beeline",0
QNWINFO: "EDGE","25099","GSM 900",33
5 в ответах регистрации означает роуминг. Это соответствует нашему профилю Orange: домашняя сеть — 20801, обслуживающая российская — 25099, Билайн. CGATT: 1 подтверждает присоединение к пакетной сети.
APN orange.m2m.spec остался прежним. После изменения проверки MCC та же eSIM, которая до этого оставалась в поиске, зарегистрировалась в российской сети.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.