Casio PD‑700 — Часть 10: частичное извлечение ROM из процессора
В отличие от остальных моделей со схожим процессором, PD‑700B имеет одну серьёзную особенность: он использует другие драйвера дисплея, информация о контрасте которым передаётся по шине данных. В RAM по адресу &H0D29 хранится 2-х байтовый адрес данных в ROM для используемого уровня контраста. Но во внешней ROM таких данных не обнаружилось, а следовательно, этот адрес указывает на внутреннюю ROM самого процессора. Этот адрес можно изменять с помощью POKE, но первая тетрада (после перестановки в big-endian) при включении или изменении уровня контраста всегда устанавливается в "8". Таким образом, для контраста фактически можно применить данные, хранящиеся в диапазоне &H8000-&H8FFF.
Теперь, когда мы понимаем принцип общения процессора с драйверами, можно попробовать перехватить данные на шине. Для этого я заказал самый дешёвый логический анализатор на Aliexpress, который оказался действительно отличным помощником.
Шина данных присутствует и на 30-пиновом порту, но мне было удобнее подпаяться к уже имеющейся карте памяти. В сервис мануале Casio FX‑8000G приведена наглядная временная диаграмма общения с таким драйвером дисплея.
Из этой диаграммы следует, что тактирование шины данных драйвер получает на вход "Ø1", а тактирование сигнала выбора - на вход "Ø2", и оно сдвинуто относительно "Ø1" на половину такта. А ещё из неё следует, что чтение одной тетрады занимает 2 такта. Для этого я также подпаялся к пинам "CS4" и "CL2A" ("CE" и "Ø1" соответственно для драйверов) на плате калькулятора.
И вот первые полученные данные. Чтобы выделить те, которые передаются драйверу дисплея, я беру значения, соответствующие времени активности "CS4", но с небольшим смещением относительно "CL2A" ("Ø1").
Тут видно, что частота тактирования составляет 307 кГц (1.228 МГц / 4), а так как для передачи тетрады требуется 2 такта, фактическая частота передачи - 153.5 кГц.
Теперь предстоит разобраться, как он эти данные собирает. Мы получили 12 байт (32 33 F3 F3 F3 FF FF FF 7F 77 87 87), но дело в том, что я считываю все 8 линий шины данных, а на драйвер поступают только 4 старшие. А учитывая то, что для передачи тетрады используется 2 такта и то, что первые тетрады каждой пары байт действительно совпадают, драйверу дисплея фактически передаётся 3 байта (3F FF 78).
Хотя младшие тетрады в данном контексте не имеют значения, мне было интересно, откуда они берутся. Чтобы это понять, я записал данные в RAM в область дисплея и проанализировал данные на шине при их отправке. Стало ясно, что младшие тетрады - это остатки старших и не имеют значения: младшая тетрада первого байта всегда "2", а далее младшие тетрады каждых 4 байт повторяют старшую тетраду стоящего перед ними байта. Кроме того, при отправке данные инвертируются, а тетрады в байтах меняют очерёдность.
Каждый следующий уровень контраста изменяет адрес с шагом в 4. Я выполнил отправку данных, изменяя шаг на 1. Так, например, уровень 31 – это адрес &H8F78, а уровень 32 – это &H8F7C, а между ними:
- [8F 78] => 3FFF78 => 3F FF78
- [8F 79] => 3FF780 => 3F F780
- [8F 7A] => 3F7800 => 3F 7800
- [8F 7B] => 3F8000 => 3F 8000
- [8F 7C] => 3F0008 => 3F 0008
Получается, что инструкции прошивки добавляют "3F" в начало, а очерёдность тетрад не меняется. Также стало ясно, что адрес указывается в тетрадах, а это означает, что считав данные из диапазона &H8000-&H8FFF можно получить только 2 кБ, хотя такой способ адресации теоретически позволяет адресовать 32 кБ.
Но логика построения данных не одинакова для всех уровней контраста: для первых 20 уровней (диапазон 8F00-8F4С), данные выглядят следующим образом:
- [8F 00] => 35 FF EF => 3 5FFE F
- [8F 01] => 3F FE 7F => 3 FFE7 F
- [8F 02] => 3F E7 BF => 3 FE7B F
- [8F 03] => 3E 7B FF => 3 E7BF F
- [8F 04] => 37 BF EF => 3 7BFE F
То есть "3" добавляется в начало, а "F" в конец, но 4 средние тетрады отправляются по тому же принципу.
Регулировка ограничена пределами младшего байта &H00 снизу и &H80 сверху. Но если перейти лимит (установить &84), то регулировка продолжает увеличивать, и младший байт по кругу доходит до &H80.
Я написал программу, выполняющую изменение контраста во всём доступном диапазоне:
10 FOR A=&H80 TO &H8F
20 POKE &HD2A,A
30 POKE &HD29,4:CONTDOWN
40 FOR I=1 TO 32:CONTUP:NEXT I
50 POKE &HD29,&H88:CONTDOWN
60 FOR I=3 TO 32:CONTUP:NEXT I
70 NEXT A
80 BEEP1
20 POKE &HD2A,A
30 POKE &HD29,4:CONTDOWN
40 FOR I=1 TO 32:CONTUP:NEXT I
50 POKE &HD29,&H88:CONTDOWN
60 FOR I=3 TO 32:CONTUP:NEXT I
70 NEXT A
80 BEEP1
Считал всё логическим анализатором и сохранил в файл. Далее написанной мной компьютерной программой выделил участки по 12 байт, начинающиеся на "32 33 ", разделил полученное на группы по 64 участка и в каждой группе из первых 20 участков выписал старшие тетрады байтов 3, 5, 7 и 9, а из оставшихся 44 участков - старшие тетрады байтов 5, 7, 9 и 11.
Поняв, что на данный момент понятного тут мало, инвертировал всё по примеру отправки данных из области дисплея. Теперь, если долго всматриваться, можно заметить некоторые константы CODRIC, записанные задом наперёд. Вспомнив принцип хранения чисел в переменных, я развернул тетрады внутри каждого байта.
Хотя, как выяснилось ранее, при установке контраста он не разворачивает тетрады, как при передаче из области дисплея, в полученном участке ROM уже можно разглядеть некоторые данные прошивки, такие как константы, команды BASIC и системные слова. Аналогичные данные также можно встретить и в прошивке PD‑1000. Видимо, прошивка в процессоре изначально хранится с обратным порядком тетрад в байтах.
Из явно различимых данных (адреса указаны в тетрадах):
- &H8100 - константы CODRIC и некоторые другие:
9F 40669599020103 => LGT(2)
88 22585168921304 => LGT(1.1)
B6 43267873133204 => LGT(1.01)
BC 86317974073404 => LGT(1.001)
A9 67626827273404 => LGT(1.0001)
3D 53441023293404 => LGT(1.00001)
3F 62756442293404 => LGT(1.000001)
99 85186044293404 => LGT(1.0000001)
BF 18737944293404 => LGT(1.00000001)
BD 61688144293404 => LGT(1.000000001)
FF 15888144293404 => LGT(1.0000000001)
FF 11908144293404 => LGT(1.00000000001)
99 30908144293404 => LGT(1.000000000001)
00 00406381398507 => ATN(1)
00 16912465689609 => ATN(0.1)
00 65666866969909 => ATN(0.01)
01 01000003011707 =>
98 00202529537401 => PI/180
- &H8686 - таблица команд и операторов BASIC с соотетствующими кодами, необходимая для токенизации:
Последняя буква - байт минус &H80.
414E474C C5 6E => ANGLE
00
424545 D0 70 => BEEP
4252 CB 9D => BRK
00
434C4541 D2 6A => CLEAR
434C D3 71 => CLS
434C4F53 C5 72 => CLOSE
434F4E5455 D0 B6 => CONTUP
434F4E54444F57 CE B7 => CONTDOWN
434F4E D4 50 => CONT
435052494E D4 64 => CPRINT
43494E5055 D4 65 => CINPUT
43524541 C4 66 => CREAD
.......
- &H8F00 - как мы уже знаем, данные для контраста:
0A10 4810 8011 4020 0620 0222 010C 800C 010E 0118 0124 0026 4026 0330 0880 0B80 0860 0184 008A 0F90 8010 0012 0014 200E 0022 001A 0030 0034 003C C03F 0078 FF7F
Всего уровней - 32, но при регулировке получается 33 из-за неправильно указанного верхнего предела.
- &H8FA8 - системные слова:
Последняя буква - байт минус &H80.
206572726F F2 => error
52656164 F9 => Ready
53544F D0 => STOP
50415353 BF => PASS?
4C4F4144204F4B A1 => LOAD OK!
4552524F D2 => ERROR
Не указывает ли наличие слов «LOAD OK!» и «ERROR» на предполагаемую возможность переноса программ с карты?
В остальных участках также прослеживаются структура и закономерности, но без полной картины разбирать это сложнее, не имеет смысла и не вызывает особой мотивации.
Я надеялся, что сначала записывается "8", а затем читается получившийся адрес (например, из-за экономии регистров), и если не позволить записать "8", то отправятся данные с установленного мной адреса. Для того, чтобы это проверить, я собрал схему на компараторах шины 74HC688, отлавливающую момент обращения к адресу &H0D2A с использованием 13 адресных линий (0110100101010):
Подключил её также к карте памяти, а её выход и пин калькулятора "WR" - к логическому анализатору. А чтобы исключить неоднозначность из-за неучитываемых старших адресных линий использовал образ RAM на 8 кБ. В результате получил следующую картину:
Отсюда следует, что сначала происходит чтение имеющегося адреса в RAM, затем новый результат записывается обратно, и только после этого выполняется передача данных с полученного адреса на драйвер дисплея.
Затем я постарался перегрузить "WR" в момент обращения к этому адресу, чтобы помешать записать результат. Ток "WR" довольно большой, и для перегрузки необходимо не более 330 ом.
Это помогло запретить перезапись, но не изменило результат. Сначала он считывает установленный мной адрес, условно &H0000, затем пытается записать следующий уровень, заменив тетраду на "8" - &H8004. После этого, независимо от того, удалось ли выполнить запись, он передаёт драйверу дисплея данные с уже вычисленного адреса &H8004.
Таким образом, на данный момент мы имеем только 2 кБ внутренней прошивки процессора, и эта уязвимость не позволяет получить больше. Но, возможно, когда-нибудь найдётся способ извлечь ещё какой-нибудь участок, и я добавлю информацию сюда, или всё же найдётся способ добраться до остальной части, и я напишу об этом полный разбор...




Комментарии
Отправить комментарий