Фантастический OpenRISC и где он обитает, или недетский разбор детской камеры

После запуска Doom на кнопочном телефоне, я искал устройства на которых можно это повторить. К ним есть требования: цветной экран и несколько мегабайт памяти (идеально 4, но можно запустить и на двух). Видел счётчик электричества и USB тестер, то и другое есть с цветными экранами. Но покупать не стал, потому что скорее всего такие устройства имеют лишь десятки килобайт памяти, как и мощный чип им не нужен. Наконец на распродаже на известном китайском маркетплейсе увидел детский фотоаппарат, его и заказал для своего извращённого развлечения реверс-инжинирингом.

Но также нашел детский фотоаппарат со скидкой в немного другом корпусе на нашем маркетплейсе, заказал и его, ведь он будет у меня много раньше чем товар из Китая. Его и начал изучать первым…

(Не)много китайского кринжа

Камера в прямом смысле ноунейм, потому что нет ни названия бренда, ни адреса завода изготовителя. Ни на упаковке, ни в микроскопической инструкции.

Несмотря на то, что кнопки данной камеры сами по себе громко щёлкают, так и при нажатии камера пищит громко и мерзко. Первым делом полез в настройки это отключать, там сразу стоял русский перевод, и с ходу не нашел настройки громкостью. Сменил язык на английский, и тогда стало понятно, что «volume» перевели как «объём».

Также в настройках нашелся номер версии чего-то: AX3295B_22–08

Что бросается в глаза — экран мерцает как старый телевизор, и глаза от этого устают. Также со стороны экрана есть дырочка в корпусе, через который светит неприкрытый светодиод и выжигает глаза если не держать камеры немного под углом.

Разрешение камеры этого чуда — 640×480. Это разрешение фильтра Байера, то есть на один пиксель камеры есть лишь один цвет, там ½ зеленых, ¼ красных и ¼ синих. Честный, не интерполированный, RGB будет лишь в разрешении 320×240. В инструкции разрешение честно не указывают, зато указывают модель сенсора камеры — GC0308, по этому названию можно найти разрешение.

Зато в настройках есть широкий выбор разрешений для фото: VGA, 1M, 2M, 3M, 5M, 8M, 10M, 12M, 16M, 18M, 20M, 40M. VGA в понимании данной камеры — это 640×480. Остальное — это натягивание совы на глобус растягивание этого разрешения на указанное в настройках. Причём тут даже обман на обмане, потому что более чем 12M (4032×2880) оно не растягивает. Наверное, это должно научить ребёнка что ничему нельзя верить.

Качество сенсора камеры отвратительное, светочувствительность очень низкая, в тени уже ничего не видно.

Настройки видеозаписи в настройках: VGA, 720p, 1080p. VGA это тот же 640×480. Видео сохраняется MJPG кодеком в AVI. Пробовал через ffmpeg закодировать видео в том же формате, но оказалось что воспроизводить оно может только записанное собой. На видео созданных через ffmpeg камера намертво зависает. Догадался, что дырочка около микро-usb разъёма — это скрытая кнопка сброса.

Может проигрывать музыку, но выхода на наушники нет, только через встроенный динамик. Который, внезапно, хорошо воспроизводит низкие частоты, что держишь в руке и ощущаешь удары и вибрации. Правда вокал звучит плохо, и в современной музыке всё сливается в какофонию. Но инструментальную классику слушать можно.

Изучаем глубже

Хотя на заказанном из Китая винты были видны прямо на фото товара, но в этом всё скрыто, так что стал изучать его поведение при подключении к компьютеру. Когда подключаете такие устройства, то лучше следить за ними через системный лог Linux, а не lsusb. Дело в том, что они могут переподключаться под другим идентификатором, или появляться лишь на доли секунды.

Сначала камера появляется в системе так:

New USB device found, idVendor=0219, idProduct=3280, bcdDevice= 1.00
Product: GENERAL - AUDIO
Manufacturer: Generic

Как только от драйвера приходит запрос SCSI — не отвечает и переподключается так:

New USB device found, idVendor=1908, idProduct=3283, bcdDevice= 1.00
Product: GENERAL - AUDIO
Manufacturer: Generic

Через SCSI приходит такая идентификация:

Direct-Access Buildwin Media-Player 1.00 PQ: 0 ANSI: 4

На этот раз работает как картридер, и бонусом как веб-камера отвратительного качества. lsusb считает что вендор 0x1908 — это Gembird.

Поиск BuildWin в интернете позволяет узнать альтернативное название — AppoTech, под таким именем у этой китайской компании есть представительство в США.

Также я подключил через так называемый загрузочный кабель (boot cable, микро-USB в разъёме которого дополнительный пятый контакт замкнут на землю, можно сделать через OTG переходник и AM-AM USB кабель). В системе появилось такое устройство:

New USB device found, idVendor=1908, idProduct=3319, bcdDevice= 1.00
Product: BLDR v1.00
Manufacturer: BUILDWIN

Через SCSI называется так:

Direct-Access BuildWin Video050Loader 1.00 PQ: 0 ANSI: 2

Это мало что даёт, так как я не нашел никакой информации как управлять этим BLDR. Остаётся только разобрать и считать прошивку программатором.

Сложности разбора

Мне удалось подковырнуть и снять декоративную панель со стороны объектива. Но под ней есть четыре углубления, три с винтами, и одно глухое. Хотя у меня есть отвёртка с мелкими битами -, но углубления настолько глубокие, что биты упираются на своём утолщении и не достают винтов. Пришлось заказывать набор тонких часовых отвёрток.

Отвинтил три винта. Также держится на пластиковых защёлках, но они не жесткие. Плата держится на еще одном винте на части корпуса с экраном.

bd28628951d9dfd65d5787f60b5200ac.jpg

Внутри оказался безымянный аккумулятор, на нём какие-то следы, будто от сорванной наклейки, на приклеенную сторону заглядывал, там тоже ничего.

2d2c683f252c932f78d4f296cdab6a62.jpg

На обратной стороне обнаружился безымянный чип с большим количеством ножек. Видимо, это и есть центральный процессор. При наклоне и подсветке фонариком, на нём таки написано 2237J, что не похоже на название, лишь на серийный номер. Даже не похоже чтобы название чипа удаляли, похоже что его никогда и не наносили.

Рядом с процессором есть чип флэш-памяти: Puya P25D16SH, который считал программатором. В паре программ не нашел чип в списках, в интернете нашел такую информацию для ручной настройки: 3.3V, 16Mbit (2MB), page: 256-byte.

Сложности дизассемблирования

Предыстория

Когда-то считывал прошивку со старого китайского телевизора. Хотел посмотреть, можно ли исправить overscan, который он делает на HDMI (не исправляется даже через инженерное меню, потому что там можно скорректировать в границах +/- 50 пикселей, а он срезает больше). Чип там TSUMV59XES, более известный просто как V59. Так вот, я не смог понять какой у загрузчика набор инструкций, а остальная прошивка сжата или зашифрована. Нашел документ со списком возможностей чипа, и там процессор описан как «High speed/performance 32-bit RISC CPU», также узнал что это чип MStar. Нашел исходники прошивок MStar для похожих чипов и там 8051 в паре с ARM или OpenRISC. Но и это не помогло, так что забросил. Зато узнал про OpenRISC.

По данным в прошивке видно, что данные выровнены по 32 бита. Числа явно little-endian. Попробовал дизассемблировать как ARM — не подходит. Попробовал MIPS — тоже не подходит. Бросились в глаза комбинации байт FC FF FF 03 и FВ FF FF 13, это похоже на команды прыжка назад, как используются в любых циклах. 26 бит константа и 6 бит опкод. Заглянул в документацию OpenRISC — так и оказалось!

Почему-то OpenRISC, в отличии от распиаренного RISC-V, это какой-то ультра-нишевый набор инструкций, его нет ни в IDA, ни в Ghidra. Пробовал собрать binutils для OpenRISC — так там вообще не проверяли поддержку little-endian OpenRISC, поэтому дизассемблер на 50% инструкций пишет, что это неизвестные. Нашел на Github модуль для IDA, написанный на питоне, но для какой-то старой версии (до седьмой). Исправил модуль чтобы запускался на седьмой и выше — нашел в процессе очень сомнительные решения и недостаток инструкций, которые есть в документации. Переписал всё полностью, за пример брал модуль msp430.py. Мой модуль здесь.

У OpenRISC есть похожая черта с MIPS — delay slot, инструкция идущая после прыжка исполняется в любом случае. Не знаю, отключается ли это на MIPS, но у OpenRISC есть специальный флаг ND (no delay), если установлен — то процессор при прыжке не выполняет инструкцию следующую за прыжком. Как оказалось, вся прошивка скомпилирована в таком режиме. Но я добавил в модуль для IDA возможность выбора режима ND через Alt-G (так же как для 32-бит ARM сделано переключение между thumb и 32-бит инструкциями).

Всего кода в прошивке ~360KB, ~250KB данных, остальная часть прошивки — это таблица офсет/размер и далее данные «типа файлов», потому что без имени, только номер в таблице. Это маленькие картинки из интерфейса в jpeg (с не удалёнными мусором метаданными от Adobe Photoshop CC 2018, что занимает ~25–35% от изображений). Также звуки в wav, несколько bmp, и неустановленные данные (наверное шрифты и локализация).

В прошивке много ссылок на данные по адресам начинающимся с 0x020xxxxx, и в самом начале прошивки я нашел установку регистра SP (stack pointer) на 0x021ffffc. Наверное 0x02000000 это начало ОЗУ, 0x021ffffc это начало стека и конец памяти, так как стек растёт к младшим адресам. Значит, предположительно, на камере 2МБ памяти.

Теперь, зная набор инструкций — можно попробовать вставить свой код и прошить программатором. Но это очень неудобно, так что я стал искать готовые инструменты или интерфейс в прошивке.

Неизвестный BuildWin

Инструментов и документации к AppoTech/BuildWin в сети очень мало, и это старые модели устройств. Но можно узнать, что они сначала промышляли контроллерами к USB накопителям. Старая серия AX2xxx — это медиаплееры с процессором на инструкциях 8051 и аппаратными декодерами mp3/jpeg. В серии AX3xxx про процессор написано «High Performance 32-bit RISC CPU». Почему-то никто не указывает OpenRISC прямо. OpenRISC считается зашкваром, или что-то с «Open» в названии это зашквар? Или, может потому что лицензия LGPL?

Из инструментов для других чипов — я узнал что используются SCSI команды из зарезервированного для вендоров диапазона (0xc0…0xff), а именно 0xcb, 0xcd.

Почему SCSI?

Похоже это удобно в Windows, так как не надо делать никаких драйверов (знаете эту проблему с поиском и установкой драйверов?). Устройство может притворятся диском, в который не вставлен накопитель. При этом есть возможность давать кастомные команды через стандартный драйвер. И прошлое компании сказывается, что начинали они с USB накопителей.

Через Wireshark записал USB трафик камеры с системным драйвером, и нашел что »BuildwinMedia-Player 1.00» это ответ на команду INQUIRY. Этот ответ я нашел в данных прошивки, и нашел код который ссылается на эти данные.»

Video050Loader» и »BLDR», из ответа на загрузочный кабель, в прошивке нет, очевидно это передаёт загрузочный ROM прописанный прямо в чипе.

«Разделённые» константы в ассемблерном коде

Немного о том, как выглядят ссылки на данные в OpenRISC коде. Если вы знаете 32-бит ARM, то там есть команды загрузки констант, что лежат где-то рядом. Таким образом эти константы просто найти без дизассемблирования. Но тут не так, загрузка 32 бит констант (адрес это тоже 32-бит константа) разделена на загрузку в старшую часть регистра, и арифметическую операцию дописывающую нижнюю часть.

Так выглядит в коде OpenRISC загрузка адреса 0x209FCA4:

09 02 80 18  l.movhi  r2, 0x209
A4 FC 42 A8  l.ori    r2, r2, 0xFCA4

Что в оригинальном коде от компилятора должно было выглядеть так:

l.movhi  r2, hi(somedata)
l.ori    r2, r2, lo(somedata)

И не обязательно, чтобы старшая часть загружалась предыдущей инструкций, это может быть 10 инструкций до.

Пришлось дорабатывать модуль дизассемблера, добавил указание целой константы в финальной инструкции:

09 02 80 18  l.movhi  r2, 0x209
A4 FC 42 A8  l.ori    r2, r2, lo(0x209FCA4)

Запуск произвольного кода

Проанализировав код прошивки, я нашел что переход в загрузочный режим не обязателен. Камера подключенная к USB с загруженной ОС позволяет выполнять произвольный код по SCSI команде 0xcd.

Также нашлась команда 0xda для перезапуска чипа в режим загрузчика.

Тут возникает вопрос, а как узнать какой код выполнять? Как прочитать или записать память? А никак, интерфейсом это не предусмотрено. Если у вас нет оригинала прошивки или дампа полученного через программатор — то никак это не найти.

Но как тогда читать/записывать память, может урезать какой-то функционал в прошивке и вместо него добавить функцию чтения? Но мне повезло, прямо до обработчика команды 0xcd идёт функция чтения/записи памяти через USB. Достаточно использовать её адрес в команде для 0xcd.

Пример SCSI запроса:

00: 55 53 42 43 # "USBC"
04: 01 00 00 00 # тег, в ответе придёт такой же
08: xx xx xx xx # размер данных (little-endian)
0с: 80 # 00 - передать данные устройтву, 80 - запросить данные с устройства
0d: 00 # флаги, не нужно
0e: 10 # размер SCSI команды (6, 10 или 16)
0f: cd # SCSI команда
10: f4 91 03 02 # адрес функции обработчика чтения записи - 0x20391F4
14: xx xx xx xx # адрес памяти для чтения/записи,
                # -1 значит использовать временный буфер
18: ff ff ff ff # 0x20391F4 позволяет вызвать еще одну функцию,
                # после чтения с компьютера, или до передачи на компьютер,
                # -1 значит ничего не вызывать
1с: 00 00 00    # не используется

И я прочитал всю оперативную память по адресу 0x02000000. Также стал искать адреса кода загрузчика из чипа, сохранил 2МБ памяти с нулевого адреса, и нашел его адресу 0x00100000, только почему-то первый килобайт затёрт нулями. Именно затёрт, потому что на него есть прыжки из кода чуть ниже. Поискав по коду загрузчика — я нашел данные »BuildWinVideo050Loader 1.00», и код ссылающийся на них. Как оказалось — код загрузчика тоже имеет вендорскую команду для выполнения кода. Но уже почему-то 0xcb. И по адресу 0x102620 нашел функцию чтения/записи памяти для вызова через команду 0xcb.

Кривые binutils для OpenRISC

Решил измерить частоту процессора, проверить запись памяти и выполнение кода. Написал код на ассемблере, скомпилировал через ассемблер из binutils. И тут сюрприз — это big-endian код. Попытался разными способами передать опции меняющие порядок байтов в компилятор ассемблера, но он не знает ничего. И похоже, такие опции для OpenRISC просто не реализованы, настолько кривая поддержка этого набора инструкций. В итоге написал программу что читает данные по 4 байта и разворачивает их в обратно порядке.

Частота процессора

Как замерить частоту процессора на неизвестном чипе? Если процессор работает на постоянной частоте и не суперскалярный, то всё просто: делаем цикл, и если мы повторим его количество раз равных частоте процессора, то код выполнится за количество секунд помноженное на время одной итерации в тактах.

Например (r5 — счётчик):

1:  l.addi    r5, r5, -1
    l.sfgtui  r5, 0
    l.bf      1b

Но мы не знаем сколько тактов уходит на одну итерацию. Поэтому нужен еще один замер, где добавим простую инструкцию, которая должна отрабатывать за один такт.

2:  l.addi    r4, r4, 0
    l.addi    r5, r5, -1
    l.sfgtui  r5, 0
    l.bf      2b
    l.jr      lr

Тогда время выполнения второго цикла должно быть на секунду больше.

Нашел пустую область памяти, записал свой код туда. Прочитал обратно — возвращает что записано. Вызвал свой код — устройство перезагрузилось. Что я сделал не так? Первая мысль была — что есть защита от исполнения, значит надо записывать поверх другого кода. Нашел крупную функцию по обработке AVI файлов. В начало поставил инструкцию возврата. А далее записал свой код. Всё записалось и читается.

Выполняю код — нет никакой задержки. Запустил несколько раз. Иногда падает. Но иногда начинает давать задержку. Вычислил частоту как близкую к 141.72MHz.

Я находил в коде значение 144000000, и сначала подумал, что это не связано с частотой процессора. Позже нашел описания AX32xx чипов на сайте AppoTech, где есть чипы с частотой 144MHz. На сайте нет упоминания чипа AX3295B — указанного в версии из опций. Тогда почему получается 141.72? Предполагаю, что приходят прерывания от таймера, что обрабатываются во время работы цикла, что тратит 2% мощности процессора. Кстати, там же указано, что у этих чипов 8 кб кэша данных, и 8 кб кэша инструкций. Правда на сайте у чипа с частотой 144MHz — 8MB памяти, а у чипа с 120MHz — 2MB, так что или в камере чип пограничный, или у него есть 8MB, но используется лишь 2MB.

Наверное, некоторые подумали, что странно что есть защита от выполнения определённых страниц памяти, но нет защиты от записи кода. Так что, наверное, защиты от выполнения нет. Но почему код то даёт задержку, то не даёт, то падает? Кэш инструкций? Но я выбирал свободное место в памяти, которое не должно было выполняться, следовательно не попало бы в кэш. И я выбирал функцию в которую вставил свой код — что тоже не должна была выполняться сразу после загрузки. Тогда что? Ведь память записывается и читается обратно. Не сразу догадался, что реально просходит — это память остаётся в кэше данных, и не записывается пока не будет чем-то вытеснена из кэша, кажется у ARM процессоров тоже есть такой режим. Эксперименты показали, что нужно записать свой код, а потом прочитать 8 кб любых данных, код стал выполняться всегда и без ошибок.

Зачем OpenRISC, когда есть знакомый ARM?

И тут, наконец, из Китая дошел фотоаппарат, что я заказывал первым.

b9a996a77de87e9a2f5a760855d2d54e.jpg

Включаю его, те же картинки, но почему-то всё как будто написанное другими людьми, но по тем же инструкциям.

472344620c4a34a2f7184f4cc259a4ab.jpg

Например:

  • На втором экран не мерцает, и даже светодиод глаза не слепит. Звук нажатия клавиш не бесит. Перевод на русский нормальный.

  • Первый работает как веб-камера, второй нет.

  • На первом на две игры больше: скролл-шутер и лабиринт.

  • На первом в тетрисе одноцветные фигуры, и их очень тяжело быстро спустить вниз, потому что нажатие вниз это сдвиг на одну клетку вниз, и зажать нельзя. Во втором фигуры разных цветов, и нажатие вниз сразу сбрасывает фигуру.

  • Первый не умеет прогирывать никакое видео, кроме записанных на нём же MJPG. Второй может проигрывать 3gp (h263) и mpeg4 (Simple Profile) c AAC звуком.

  • Первый при проигрывании музыки показывает плейлист (нажать влево), второй не показывает ни списка треков, ни даже названий.

  • У первого есть накладные фильтры в фото режиме, и даже зум при зажатии вверх-вниз, у второго ничего.

  • Понимание мегапикселей в опциях разное (тоже обман, но чуть по другому).

  • Второй работает как картридер очень медленно (USB 1.1?).

И даже разбирается второй очень легко, биты не застревают, никаких пластиковых защёлок нет:

05ae69e9ea79fc1d4dce311608323bf2.jpg

Хоть я и не замерял размеры, но на вид печатные платы внутри обоих моделей имеют одинаковую форму и размер, и расположение всех кнопок и разъёмов одинаковое. Так что для меня это выглядит так, что можно поменять корпуса местами и всё подойдёт по размеру.

Версия прошивки из опций второй камеры привлекла внимание. Потому что в начале подписано «MMI Version», не помню уже что это сокращение означает, но встречалась лишь на телефонах, значит ли это…

Подключаю к USB — производитель Spreadtrum. И это оказался тот самый чип SC6531, на котором я уже запускал Doom год назад. Только не определяется экран — нашел коды инициализации экрана и добавил поддержку в свой порт Doom.

Но что делать с управлением, на этой камере всего 6 кнопок, это стрелки, и две кнопки со смыслом enter/escape. Причём одна кнопка — это кнопка питания, а она идёт отдельно от остальных со странным интерфейсом. Пришлось добавлять поддержку кнопки питания.

Из шести кнопок я сделал 10, зажатая кнопка питания изменяет функциональность остальных (как Fn на клавиатуре). Этого хватает для игры в Doom.

Результат на видео:

А что с OpenRISC?

Пожалуй, что самое интересное я сделал — доступ к процессору устройства получил. Сделал модуль дизассемблера для IDA. Писать драйвера под его железо и портировать Doom на него — будет рутиной. Тем более что надо будет собирать компилятор (старый) из коммитов на гитхабе, а там еще какие-нибудь сюрпризы обнаружатся, как с binutils. Надо придумывать как уместить Doom в 2 мегабайта. А я уже запустил Doom на детской камере на привычном мне железе.

P.S.: Не удивлюсь, что если есть на чипе Spreadtrum, то есть и версия на Mediatek MT6261, которые также популярны в кнопочных телефонах. Может даже на чипах RDA (которые тоже в кнопочных есть, но реже Spreadtrum и Mediatek). Зачем портировать одну и ту же задачу на разные чипы, и каждый раз это делают разные люди, и делают всё по своему — я не понимаю. Причём, у одних одно лучше получается, у других другое.

© Habrahabr.ru