Всем привет!
Краткое предисловие: я счастливый обладатель замечательного саундбара YAS-109 от Yamaha, на момент написания пользуюсь им уже целый год, и всё в целом хорошо. Но однажды я решил узнать: не подслушивает ли меня мой музыкальный друг? Ведь у него есть встроенная поддержка Alexa, а ещё Bluetooth, WiFi, Ethernet и другие прелести… Так и начинается история моего ресёрча.
Пробуем простой подход
Обычно самый простой способ изучить поведение железки — это посмотреть на файлы обновлений. Уже по ним можно сделать некоторые выводы о безопасности устройства, о подходе производителя к этой самой безопасности. Так я и сделал: полез в поиск, вбил название устройства, добавил слово firmware
и первой же ссылкой получил страницу с файлами обновления:
В архиве меня ждёт следующий набор файлов:
На скрине видим четыре файла: mcu.enc
, update.enc
, yamaha_usb_upgrade.enc
и yamaha_ver.txt
. Конечно, это были никакие не файлы Wireshark, как рисует мне «Проводник», просто расширение файла совпадает. Это же расширение говорило мне и о том, что сами обновления, скорее всего, чем-то пошифрованы. Проверим? Суём файлы в hex-редактор и видим следующую картину:
В самом начале можно заметить не очень приятное слово Salted__
, что означает: смотри, мол, я зашифрован OpenSSL с использованием «соли». Чтобы забрутить это дело, могут уйти десятилетия, поэтому подход пришлось признать неудачным.
Пробуем сложный, но самый эффективный метод
Я с детства любил ковыряться в различных железках: тамагочи, гоночных машинках, старых магнитофонах. Разобрать такое, потом не собрать, получить за это по шапке от бати — вот моё счастливое детство. Прошли годы, и теперь «ковыряться в железках» — моя профессия. Конечно, сейчас на работе желательно собирать их обратно, но поскольку дают обычно два экземпляра, один идет под разбор.
Так вот, я решил разобрать и свой саундбар. Штука это тяжёлая, с обилием ацетатного скотча и клея внутри, чтобы ничего не вибрировало от басов.
Переворачиваем «колонку», выкручиваем все винты, запоминая, где какой длины они были. Заранее скажу, что винты, которые нам потребуется выкрутить, будут трёх форматов: длинные (корпус плюс пластиковая крышка внутри), короткие (корпус и все остальные крепления внутри) и один мелкий, удерживающий плату HDMI-аддона.
При разъединении двух половинок корпуса действуем аккуратно: сенсорная панель саундбара соединяется с основной платой управления гибким шлейфом плюс двумя проводами: заземлением и питанием.
Переходим к основной плате управления. Часть её уходит под пластиковый корпус с перфорацией, поэтому снимаем его и любуемся платкой целиком. А встречает нас тут непонятный ABOV A33G526RLN
. Поиск выдаёт информацию о каком-то 32-bit-Cortex-M3-based-микроконтроллере. Смотрим распиновку:
Сразу отмечаем для себя наличие ног JTAG: nTRST
(49), TDI
(50), TMS
(51), TCK
(52), TDO
(57), nRESET
(58). Так как корпус контроллера у нас не BGA-шный, каждый из указанных выводов можно прозвонить и найти, куда именно они выведены (если выведены). Достаточно быстро удалось выяснить, что ноги JTAG приходят на такой вот разъём:
Этот разъём практически идентичен соседнему с ним — чёрному шлейфовому разъёму. Такого коннектора у меня, конечно же, не было, как и шлейфов нужной ширины, поэтому я просто подпаялся к необходимым ногам:
Затем я припаял к МГТФ-проводам проводки с разъёмом «мама» и подключил их к JLink, согласно распиновке. Без особой надежды на успех (мало ли, отключен JTAG) я запустил железку. И, о чудо, JLink Commander встретил меня сообщением об успешно найденном Cortex-M3 контроллере:
Смотрим Memory Map в даташите:
Видим, что основной код лежит в «нуле», а загрузчик — в 0x1FFF_0000
. Окей, пробуем сдампить. К сожалению, если читать память во время работы, в «нуле» нас будут ждать нули же. Поэтому делаем reset
и читаем снова:
Как и ожидалось, первой прочиталась обычная таблица векторов ARM-контроллера. Обрадовавшись, я полез изучать полученный код, но, к сожалению, не нашёл ничего о расшифровке обновлений. Значит, с этими обновлениями работает кто-то другой…
Попробуем снова посмотреть на плату управления, вдруг можно сдампить что-то ещё.
Оказывается, можно. Нашлась флешка в корпусе SOIC8
с маркировкой BoyaMicro 25Q32BSSIG
:
Засовываем флешку в программатор и видим что-то, чего увидеть совсем не ожидаем, а именно:
Первое, что бросается в глаза: хедер MAPX
и никакого внятного содержимого внутри — возможно, здесь хранятся какие-то конфиги. Ладно, идём дальше, здесь ловить нечего.
А дальше — больше. Под припаянным экраном с поролонкой я нахожу интересный контроллер с маркировкой CSR A67175
:
Поиск по данному названию приводит меня на сайт Qualcomm с чем бы вы думали… с упоминанием технологии MAPX
! Конечно же, публично доступных даташитов на данную приблуду не нашлось, скачать что-либо можно только при наличии полноценного подтверждённого аккаунта на сайте вендора, притом на обычного человека оформить его нельзя. Ну что ж… Понадеявшись, что всю основную работу с обновлениями делает не этот монстр без документации, я пошёл изучать плату дальше.
Помните тот модуль, который втыкается в штырьевой разъём на основной плате, с наклейками с указанием сетевых MAC-адресов, экраном и коннектором для антенны WiFi?
Раньше я его отметал как просто модуль WiFi, а, значит, не содержащий в себе ничего интересного. Да и выпаивать такой большой экран не очень хотелось. Но раз больше на плате смотреть нечего, я решил изучить этот модуль детальнее.
На обратной стороне я обнаружил крайне интересный объект:
Это была NAND флеш-память Kioxia TC58NVG1S3HTA00
. К сожалению, на тот момент для моего программатора ChipProg-48
не было необходимого адаптера, а с обычной универсальной для TSOP48
колодкой флешку читать он отказывался. Пришлось обращаться к «дяде» по объявлению.
Заполучив дамп на USB-флешку, дома я первым делом полез смотреть, что же там. А ждал меня полнейший сюрприз:
Как видите, полнейший мусор. Расстроившись, я даже успел позвонить «дяде» и высказать всё, что о нём думаю, а именно: что за ерунду он мне сдампил? Наверняка программатор кривой, или контакт плохой или колодка не та. На что мужик мне ответил, мол, приходи завтра, попробуем сдампить снова и хотя бы сравнить дампы. Если читается мусор, тогда они не совпадут. Согласившись с этим утверждением, я немного успокоился. Но дожидаться завтрашнего дня не стал, а решил пристальнее рассмотреть содержимое дампа.
Увидев чуть дальше по файлу большие блоки из FF
, я понял, что дамп скорее корректный, чем нет, и предположил, что имею дело с каким-то шифрованием. Как и что именно было пошифровано я, конечно же, не знал, поэтому решил пойти новым для себя методом.
Я открыл даташит на флешу, отметил для себя организацию ячеек памяти — блоков и страниц: (2048 + 128) bytes × 64 pages × 2048 blocks
. Далее я сделал предположение, что шифрование может применяться постранично или по блокам. Разбив дамп на блоки, я решил поксорить их между собой и выделить те, где оказалось бы наибольшее количество нулей, попутно сохраняя такие результаты. И, что интересно, этот подход дал свои плоды.
В некоторых результатах ксора появились более-менее читаемые строки:
Продолжив играться дальше, я выяснил, что первые 0x440
байт, поксоренные на третьи 0x440
байт, дают наиболее правдоподобный результат. Возможно, если поксорить второй такой блок с четвёртым, я тоже получу что-то вменяемое? Оказалось, что так оно и есть. Чуть позднее я выяснил, что первый блок можно также успешно поксорить и на 256-й блок. Написав скриптец для автоматизации сего действия, я принялся изучать полученные результаты. Но они меня не обрадовали.
Чуть далее по файлу закономерность работать перестала. Да и странно это, что для шифрования данных на флеше используется фактически её половина. Наверняка алгоритм был каким-то другим. Тем не менее тех данных, которые удалось наксорить, мне хватило для того, чтобы обнаружить ARM-овский код, который шёл следом за вот таким блоком с упоминанием слова BOOTLOADER
:
Я загрузил этот код в IDA Pro (архитектура ARM64
) и принялся его изучать. Кое-как подобрав ImageBase
(он оказался равен 0x400
), я бегло осмотрел распознанные функции и данные, которые в них используются. Конечно же, я наткнулся на список с маркировками NAND флеш-памяти, в том числе и имеющейся у меня:
Но ссылок на этот список IDA не нашла. Тем не менее прямо над ним нашлась крайне подозрительная табличка:
Что первым делом делает реверс-инженер, когда встречает незнакомые константы? Правильно, идёт с ними в поисковик. И, что забавно, константы нашлись на GitHub! Как раз в контексте NAND, а рядом упоминались такие слова, как randomizer
и scrambling
. Изучив тот код, я пришёл к выводу, что эти константы являются начальными значениями для генератора псевдослучайных данных (ключа) для шифрования данных на флеше. К сожалению, сама логика генератора с ходу не нашлась, но надежда на то, что я смогу побороть шифрование данных в дампе, появилась, и достаточно высокая.
Продолжив искать данные константы, я наткнулся на другой интересный репозиторий от ValveSoftware с названием steamlink-sdk. В этом репозитории данные константы использовались уже не как значения, которые отправляются в контроллер NAND, а тот их непонятно как использует. Здесь обнаружился… сам код генератора. Файл с именем prbs15.c
содержал и саму таблицу сидов с названием seed_table
, и такую функцию, как prbs15_gen
, в которой эта таблица использовалась. Я незамедлительно переписал себе код функции на Python, запустил скриптец с крайне большой надеждой на положительный результат.
И… генерируемый скриптом ключ таки совпал с тем, что мне удалось получить, ксоря блоки между собой! Я ликовал!
Как оказалось, каждый сид использовался для генерации ключа размером 0x440
байт, который применялся на два блока такого же размера подряд, после чего брался следующий сид. Так расшифровывалась целая страница размером 0x22000
байт, после чего сиды снова брались с нулевого элемента. Итого получается, что реально использовалась лишь первая половина сидов из таблицы (64
элемента).
Наверняка к этому моменту у вас мог возникнуть вопрос: а почему вообще приём с ксором блоков мне помог? Дело в том, что в расшифрованном дампе оказалось очень большое количество блоков размером 0x440
байт, заполненных одними лишь нулями, либо FF
. Если бы в дампе не нашлось таких блоков, данный подход бы не сработал. Пришлось бы что-то придумывать, например сравнивать дампы с флеши после каждого включения саундбара.
Особенности NAND-памяти
Вы наверняка уже и забыли о «дяде» и его предложении перечитать флешку. А я не забыл. К сожалению, вспомнить о нём мне пришлось, когда в некоторых местах расшифрованного дампа стали обнаруживаться не очень хорошие артефакты:
Как можно заметить, в двух разных блоках, данные в которых должны быть одинаковыми, некоторые строки оказались битыми. Так как в правильности ключа я был уверен, оставалось одно — местами битый дамп. Когда я спросил об этом товарища, он сказал, что это нормально и, возможно, стоит поиграться с изменением подаваемого при чтении флешки напряжения. Именно такие действия применяют умные ребята из Rusolut в своём видео об особенностях дампинга NAND.
На тот момент у меня по-прежнему не было нужной колодки для программатора, поэтому я снова созвонился с «дядей» и попросил его наделать для меня дампов при других подаваемых напряжениях. Получив в распоряжение 10 новых дампов, я принялся их сравнивать. Вы будете смеяться, но все были разными, при том биты в каждом менялись на абсолютно разных позициях! Полнейший треш…
С полученным осознанием безысходности и расшифрованным битым дампом, я принялся его изучать, первым делом натравив на него binwalk
. Среди результатов поиска обнаружились сигнатуры UBIFS
, скомпилированные device-tree
блобы и много чего ещё. Я решил начать с device-tree
.
Не буду сейчас вдаваться в структуру данного формата данных. Скажу лишь, что для каждого раздела, хранимого в структуре, хранится и SHA256
-хеш, по которому можно определить целостность содержимого. После извлечения всех device-tree
блобов и разделов из них, я стал их сравнивать. Так мне удалось обнаружить несколько разделов в разных блобах, отличающихся всего на некоторое количество бит. Найдя те разделы, у которых реальные хеши совпали с указанными в блобах, я взял их за основу при сравнении с теми, чьи хеши отличались. Таким образом мне удалось исправить биты в кривых разделах и немного починить оригинальный дамп.
Пришла пора переходить к UBIFS
. На просторах интернета я нашёл скрипт ubidump.py и натравил его на расшифрованный дамп, чтобы тот попытался извлечь всё, что можно было. Но не тут-то было. В структуре блоков UBI
имеется поле с контрольной суммой CRC32
, и если она не совпадает, то дампер такой блок пропускает. К сожалению, таких блоков было очень много и нужно было что-то придумать.
А придумал я очень простую, но действенную вундервафлю:
Тут всё просто: если контрольная сумма не совпала, то «переключаем» один бит и снова считаем CRC32
. Конечно, это не будет работать, если в данных, для которых идёт подсчёт, таких неправильных битов больше одного, но такого при сравнении дампов NAND я не обнаружил. Так, потихоньку, бит за битом, мне удалось исправить большое количество кривых байт!
Тем не менее, проблемы с UBIFS
не закончились. Если вкратце, то в некоторых местах, где должны были быть блоки UBI
, хранился какой-то мусор, который раньше я принимал за упакованные данные (файловая система позволяет хранить данные либо в виде потока ZLIB
, либо сжатые LZO
). Что это не сжатые данные, я понял, когда заметил цикличность в появлении мусора: начинались они со смещения 0xBB00
и повторялись каждые 0x22000
байт.
Неужто генератор ключа работает неправильно? Перепроверил — вроде всё нормально. Тогда в чём может быть проблема? Ответ оказался полнейшим выносом мозга! Дело в том, что контроллер NAND от MediaTek, прошивки от которого у меня не было, решил, что ему мало просто постранично что-то там скремблить. Надо ещё немного извернуться…
Так вот, умные ребята-разработчики прошивки этого контроллера решили, что нужно брать 22
-й блок в каждой странице и шифровать его по-особому. А именно: брать 63
-й сид из таблицы сидов, генерировать по нему ключ, но использовать байты получившегося ключа со смещения 0x118
(SIC!!!). Не спрашивайте, как я к этому пришёл. Но в итоге дамп стал получаться нормальным!
А вот что именно там обнаружилось, я расскажу в другой раз. Но для затравки: код там такой, что за голову хватаешься… Вот вам для подогрева интереса:
P.S. А ещё саундбар работает с сетью даже тогда, когда выключен (но воткнут в розетку).
Комментарии (34)
pnetmon
27.09.2023 14:26+4P.S. А ещё саундбар работает с сетью даже тогда, когда выключен (но воткнут в розетку).
а еще он включается со смартфона и в требованиях к приложению беспроводная локальная сеть
ImagineTables
27.09.2023 14:26+8Автор, не томи!!! Сифонит?
VelocidadAbsurda
27.09.2023 14:26+12Красота! Трюк с исправлением битов, ориентируясь на CRC, отдельно порадовал. Жду продолжения!
"Шифрование" NAND - не ради сокрытия, а просто чтобы устранить длинные последовательности одинаковых битов (в NAND высокой плотности такие сгустки зарядов могут портить соседние биты), стандартная функция многих NAND-контроллеров. И та странность с другими сидами в каждом n-ном блоке, возможно, учитывает физическую организацию некой конкретной модели NAND. Алгоритмы эти, бывает, всплывают на форумах Acelab и подобных софтов по восстановлению данных.
HenryPootle
27.09.2023 14:26+2На каком чудовищно сложном фундаменте стоит вся современная цивилизация! С виду - просто колонка, усиливающая звук телевизора, а автор начал разбираться - и какие бездны вершин открылись.
Костыли, примотанные изолентой к велосипеду с квадратными колёсами, едущему по специально спроектированной под квадратные колёса мостовой. И ведь это всё работает, мы это всё ежедневно носим в кармане, это нас возит в автомобилях и отправляет в полёт на самолётах!
SSLHTML
27.09.2023 14:26+2Много очень интересных моментов в статье, особенно для меня. Понравился метод "догадки" поксоренных друг на друга ячеек, да и вообще работа с дампом памяти, снятого с NAND в TSOP48. Сам, практически ежедневно работаю с этим типом памяти. Проберы в использовании дизассемблеров разного толка ограничивают мои возможности, но когда для специалиста @DrMefistO это не проблема, то расследование становится и вправду очень интересным.
Большое спасибо за подобные статьи с исследованиями, разработками и изысканиями. Они помогают тем, кто интересуется и стремиться вырасти в реверсинге. Спасибо также за этот вклад, как лично в мою копилку знаний, так и в ресурс Хабр.
Deito
27.09.2023 14:26То, что данные улетяют без спроса очевидно, интересно что будет с этим сделано. Будет ли патч на это безобразие.
DrMefistO Автор
27.09.2023 14:26Написал вендору, а как там будет дальше неизвестно.
User79
27.09.2023 14:26+2Ещё на зарубежные технические ресурсы статью разместить для получения огласки => ускорения реакции Yamaha
IvanPetrof
27.09.2023 14:26А по тамошним законам это законно? Не засудят?
DrMefistO Автор
27.09.2023 14:26+1Ну, текущая статья не содержит ничего такого. Публиковать эксплоиты я не планирую. Разработчик уведомлен, жду реакции. В следующей статье без его ответа я могу лишь опубликовать то, что не может привести к удалённому взлому устройства, а в личных целях никто не запрещает это делать.
event1
27.09.2023 14:26+1Наверняка к этому моменту у вас мог возникнуть вопрос: а почему вообще приём с ксором блоков мне помог? Дело в том, что в расшифрованном дампе оказалось очень большое количество блоков размером
0x440
байт, заполненных одними лишь нулями, либоFF
.А я вот не понял, честно говоря. Вы же код взяли не из тех блоков которые были заполнены нулями или FF. Как же вам помогли пустые блоки? Или они вам помогли обнаружить периодичность?
И ещё, в порядке товарищеской критики: по тексту немного непоследовательно используется термин "блок". Сам NAND делится на блоки стирания (erase block), которые в свою очередь делятся на страницы (pages). Блоки по 0x440 байта было бы удобнее назвать как-нибудь иначе, чтобы не путать с блоками стирания по 0x22000. Например, "сегменты".
DrMefistO Автор
27.09.2023 14:26+1Спасибо за дельную критику! Я, пожалуй, не стану менять уже имеющееся именование. В даташитах нет упоминания деления на блоки "сегменты" по 0x440, как и того, что в каждом из них представлена Spare Area. Думаю, у читателя это не должно вызвать особой путаницы.
Касательно заполнения нулями и FF: да, там практически в самом начале расшифрованного дампа идёт огромный кусок, заполненный 00, а если точнее, то до 0x407F4 (это уже не включая вырезанные 0x40 в каждом "сегменте"). Так что мне, по сути, просто повезло встретить такое большое количество нулей.
event1
27.09.2023 14:26+1В даташитах нет упоминания деления на блоки "сегменты" по 0x440, как и того, что в каждом из них представлена Spare Area
Я думаю, что его и нет. Во всяком случае те чипы, что мне попадались меньше страницы не делились. Скорее всего это фишка NAND-контроллера. Возможно, это самый маленький размер страницы встречающийся среди поддерживаемых NAND-чипов, вот они и работают сегментами такого размера.
DrMefistO Автор
27.09.2023 14:26Скорее всего так. До прошивки NAND-контроллера я тоже доберусь. Уже нашёл его JTAG.
VelocidadAbsurda
27.09.2023 14:26+2Да, это самодеятельность NAND-контроллера. К примеру, из него наружу хотят классическими секторами по 0x200, сам NAND имеет страницы по 16К, контроллер использует схему ECC по 11 байт на сектор - ну и чхать ему на некрасивые смещения, забивает физические 16K кусками данные+ECC по 0x20B байт подряд без стыков, не разбираясь, где там по задумке производителя NAND данные, где spare, у него в сторону NAND потоковый интерфейс со счётчиками байтов, разделяющий данные/ЕСС на лету, достаточно только задать ему размеры того и другого. Плюс, внимание, где-то не в начале и не в конце физ. страницы может находиться bad block marker (!=FF - блок дефектный, смещение определяет производитель, он сразу на фабрике помечает дефектные блоки. Смещения встречались ну очень некруглые), приличный NAND-контроллер и его на лету отделяет, как ни в чём ни бывало. В итоге на самом нижнем уровне формат может быть ну очень причудливым.
И вишенкой на торте: в NAND, с которого какой-нибудь SoC напрямую грузится в embedded Linux, запросто может быть несколько зон с разными форматами страниц: BootROM понимает какой-нибудь свой причудливый формат, первичный загрузчик записан в нём, вторичным взяли какой-нибудь готовый U-Boot, организующий софтово свой формат, ядро Linux лежит в нём, а в самом ядре в драйвере ФС ещё какой-нибудь третий, и ФС - в нём :)
NickWinter
27.09.2023 14:26+1Спасибо за статью! Я все такие устройства в отдельную сеть с изоляцией друг от друга добавляю. Пускай сам себя сканирует. Ну и DNS-фильтрация
mobilz
норм затравочка )