Защита ПО процессора S805-B
Речь пойдёт о способе защиты програмного обеспечения, который реализован в самом процессоре. В качества экспериментов я выбрал мультимедиа приставку Comigo Quattro. Цель — запустить своё ядро линукс.
Краткий обзор
На первый взгляд ПО приставки является клоном Android. Доступ по ssh закрыт. Работает только с действующим абонементом от дистрибьютора. Стоковая прошивка полностью шифрована. Всё глухо, как в танке.
Подготовка
Первым делом я подробно ознакомился с техническим описанием процессора. Порадовали меня две вещи: у процессора был интерфейс UART и он поддерживал загрузку системы с разных источников (USB, SD и т.д.), которая могла настраиваться с наружи при помощи конфигурационного регистра. На борту приставки также имелось место для mSD карты, но разьём был не припаян. Для того, чтобы найти пятачки конфигурационного регистра и пины UART мне пришлось спаять процессор и прозвонить дорожки. Когда все контакты были найдены я подключил адаптер UART и запустил приставку. На экрана высветился U-Boot. Загрузка системы прерывалась нажатием клавиш и U-Boot выходил в режим ввода, но ни на какие клавиши, кроме enter никак не реагировал. И эта лазейка тоже была закрыта.
QA5:A;SVN:B72;POC:17F;STS:0;BOOT:0;INIT:0;READ:0;CHECK:0;PASS:0;
no sdio debug board detected
TE : 1583722
BT : 19:30:15 Apr 13 2015
PMU:NONE
##### VDDEE voltage = 0x044c
CPU clock is 792MHz
DDR mode: 32 bit mode
DDR size: 1GB
DDR check: Pass!
DDR clock: 792MHz with 2T mode
DDR pll bypass: Disabled
DDR init use : 13644 us
HHH
Boot From SDIO C
SD_boot_type: 00000002
card_type: 00000003
0x0000009f
Aml log : M8-R2048 TPL pass!
ucl decompress...pass
0x12345678
Boot from internal device 1st tSD/fSD on SDIO C
TE : 1867612
System Started
==================COMIGO BOOTLOADER==================
==========12fc92:S:Enc (Apr 13 2015 - 19:29:55)==========
clr h-ram
DRAM: 1 GiB
relocation Offset is: 2febc000
show partition table:
part: 0, name : logo, size : 800000
part: 1, name : recovery_bak, size : 1000000
part: 2, name : recovery, size : 1000000
part: 3, name : boot, size : 1000000
part: 4, name : system, size : 32000000
part: 5, name : data, size : 8c000000
part: 6, name : cache, size : 20800000
part: 7, name : sec_gpt, size : end
aml_card_type=0x100
MMC: out reg=c1108058,value=fffcf800
out reg=c1108058,value=fffcfa00
[mmc_register] add mmc dev_num=0, port=1, if_type=6
[mmc_register] add mmc dev_num=1, port=2, if_type=6
SDIO Port B: 0, SDIO Port C: 1
power init
out reg=c110804c,value=dfffffff
IR init done!
register usb cfg[0][1] = 3ff6fcf4
register usb cfg[2][0] = 3ff72a6c
NAND: EMMC BOOT: not init nand
do not init nand : cause boot_device_flag without nand
get_boot_device_flag: init_ret -1
get_boot_device_flag EMMC BOOT:
init_part
Emmckey: Access range is illegal!
Emmckey: Access range is illegal!
Unknown partition type on device 'SDIO Port C'
Device 'SDIO Port C' wp size=8388608 port=2
[mmc_init] SDIO Port C:1, if_type=6, initialized OK!
mmc_device_init
mmc_get_partition_table
Start mmc_get_partition_table
Partition table get from SPL is :
name offset size flag
===================================================================================
0: pri_gpt 0 800000 0
1: env 800000 800000 0
2: reserved 1000000 4000000 0
3: logo 5000000 800000 1
4: recovery_bak 5800000 1000000 1
5: recovery 6800000 1000000 1
6: boot 7800000 1000000 1
7: system 8800000 32000000 1
8: data 3a800000 8c000000 4
9: cache c6800000 20800000 2
10: sec_gpt eb800000 800000 0
mmc read lba=0x8000, blocks=0x1
mmc read lba=0x8001, blocks=0x1
mmc_read_partition_tbl: mmc read partition OK!
eMMC/TSD partition table have been checked OK!
i=0,register --- emmc_key
MMC BOOT, emmc_env_relocate_spec : env_relocate_spec 59
set_storage_device_flag: store 2
[imgread]Secure kernel sz 0x5b36a0
Aml log : M8-R2048 IMG pass!
vpu clk_level in dts: 3
set vpu clk: 182150000Hz, readback: 182150000Hz(0x701)
Net: Meson_Ethernet
init suspend firmware done. (ret:0)
cvbs trimming.1.v5: 0xa0, 0x0
upgrade_comigo_environment: expect 6 active 6
init_comigo_environment
type:flash,start to read mac...
device init start
aml_keys: version 0 can not be init 3ff72c68
current storer:emmc_key
flash init key ok!!
init flash success
all key names list are(ret=18):
uuid
serialno
mac
4:3:3:0:3:a:4:2:3:3:3:a:3:3:3:9:3:a:3:0:3:2:3:a:3:1:4:6:3:a:3:4:3:9:
mac is: 43:30:3a:42:33:3a:33:39:3a:30:32:3a:31:46:3a:34:39:
read ok!!
read mac success,mac=C0:B3:39:02:1F:49
androidboot.mac is exist in bootargs, mac=C0:B3:39:02:1F:49 androidboot.serialno=A0652602C5706198 androidboot.uuid=4a3842462f47694d364b6f544d3236596b34374e3977
BOARD VERSION=2
reboot_mode=charging
hdcp get form storage medium: auto
don't found keyname,uboot_key_read:1634
prefetch hdcp keys from auto failed
AKSV invalid
hdmi tx power init
mode = 6 vic = 4
set HDMI vic: 4
mode is: 6
viu chan = 1
config HPLL
config HPLL done
reconfig packet setting done
key save in emmc
key size=44
the key name is :
the key data is :4a3842462f47694d364b6f544d3236596b34374e3977
key size=32
the key name is :serialno
the key data is :41303635323630324335373036313938
A0652602C5706198
efuse version is not selected.
Hit ENTER key to stop autoboot: 1 tstc enter
exit abortboot: 1
COMIGO#
С оригинальным U-Boot’ом на этом этапе сделать ничего было нельзя и я решил проверить загрузку с mSD карты. Для этого я припаял разьём и соединил пару контактов конфигурационного регистра с массой, чтобы процессор грузил систему с карты. Новый U-Boot был от экспериментального борта ODROID-C, который базировался на таком же процессоре.
Собрав всё это, я вставил SD карту, включил приставку и… увидел такое:
SERIAL:4;STS:0;BOOT:1;INIT:0;READ:0;CHECK:FFFFBF00;USB:3;
И этот текст повторялся каждую секунду вновь и вновь. Процессор вовсе и не собирался грузить мой U-Boot и это меня огорчило.
Поиск причины
Мне стало ясно, что процессор — это первая инстанция, которая проверяет код на подлинность. Параметр CHECK: FFFFBF00 указывал на то, что код, загруженый с mSD карты более чем полностью по каким-то причинам не устраивал процессора. Для сравнения, загрузка оригинала начиналась в свою очередь так:
QA5:A;SVN:B72;POC:17F;STS:0;BOOT:0;INIT:0;READ:0;CHECK:0;PASS:0;
Что же проверял процессор? Этот ответ я надеялся найти в сурсах U-Boot’а для AMLogic. После недолгих поисков, я увидел, что SPL часть от U-Boot’a заканчивается вот такой структурой
typedef struct {
unsigned int nSizeH; ///4@0
struct st_secure{
unsigned int nORGFileLen; ///4@4
unsigned int nSkippedLen; ///4@8
unsigned int nHASHLength; ///4@12
unsigned int nAESLength; ///4@16
unsigned char szHashKey[32];//32@20
unsigned char szTmCreate[24]; //24@52
unsigned char szReserved[60]; //60@76
}secure; //136@136
unsigned char szAES_Key_IMG[60];//60@136
unsigned char szTmCreate[48]; //48@196
unsigned int nSizeT; ///4@244
unsigned int nVer; ///4@248
unsigned int unAMLID; ///4@252
}st_aml_chk_blk; //256
Небольшое отступление. Двухступенчатая загрузка системы очень часто применяется на встроеном оборудовании. Т.е. ROM процессора грузит сначала небольшую часть кода (так называемый SPL) и передаёт ему управление. Тот в свою очередь после основных настроек грузит вторую, большую часть (TPL) и передаёт ей управление. Ну, а TPL после окончательных настроек грузит ядро и запускает его.
Забегая немного вперёд, скажу, что SPL состоит из 32КБ, в конце которого находится структура st_aml_chk_blk, которая в расшифрованом виде выглядит вот так:
Стало быть этот блок данных использует процессор для проверки SPL.
В поисках решения
Чтобы заставить процессор загрузить мой U-Boot, я испробовал множество вариантов. Но все они не приносили желаемого результата — процессор упорно выдавал CHECK: FFFFBF00. Я постепенно приходил к выводу, что защита сделана на 100% и дыр нет.
После очередной неудачной попытки я оставил приставку и пошёл на кухню (за чаем, конечно), раздумывая подключать JTAG, а когда вернулся, меня ждал вот такой результат:
SERIAL:4;STS:0;BOOT:1;INIT:0;READ:0;CHECK:FFFFBF00;USB:3;SERIAL:4;STS:0;BOOT:1;INIT:0;READ:0;CHECK:FFFFBF00;USB:3;SERIAL:4;STS:0;BOOT:1;INIT:0;READ:0;CHECK:FFFFBF00;USB:3;SERIAL:4;STS:0;BOOT:1;INIT:0;READ:0;CHECK:FFFFBF00;USB:3;SERIAL:4;STS:0;BOOT:1;INIT:0;READ:0;CHECK:FFFFBF00;USB:3;SERIAL:4;STS:0;BOOT:1;INIT:0;READ:0;CHECK:FFFFBF00;USB:3;SERIAL:4;STS:0;BOOT:1;INIT:0;READ:0;CHECK:0;PASS:1;
-----------------------------------------------------------------------
* Welcome to Hardkernel's ODROID-C... (Built at 19:33:00 Dec 8 2014) *
-----------------------------------------------------------------------
CPU : AMLogic S805
MEM : 1024MB (DDR3@792MHz)
BID :
S/N :
***** Warning!! *****************************************************
* This board have not been autorized or product keys are not valid. *
* Please contact with Hardkernel or your distributor *
*********************************************************************
О как! После некоторого времени (это было примерно секунд 40) процессор выдавал CHECK:0; PASS:1; и грузил мой U-Boot. Он конечно зависал потом, но это не имело значения, доступ к системе был получен.
Анализ
Первым делом я написал программу, которая сделала дамп ROM’а процессора и фьюза (это такой участок памяти в процессоре, который может быть прошит только один раз. Здесь как раз и хранятся ключи AES, public RSA и некоторые конфигурации процессора). Заполучив таким образом ключ AES я расшифровал оригинальный U-Boot и проанализировал ROM. Принцип загрузки SPL был таков: ROM первым делом считывал конфигурацию из фьюза и сохранял её как переменную. Далее он грузил 32 КБ с флэшки в память и смотрел, что показывала конфигурация на предмет проверки сигнатуры и шифрования. В нашем случае ROM затем вычитывал экспоненту e и модуль N из фьюза, собирал PublicKey (e, N) с которым он расшифровывал st_aml_chk_blk, затем вырешивал sha2 от SPL без st_aml_chk_blk и сравнивал то что получилось с ключом из st_aml_chk_blk. И в случае несовпадения дальнейших действий не предпринимал. Так что же заставило процессор выполнить мой SPL? Сигнатура ведь по-любому не совпадала. Для поддержки RSA в ROM была включена библиотека PolarSSL. ROM управлял свободной памятью исползуя собственный механизм (примерно как malloc и memfree). Перед использованием функций из библиотеки PolarSSL ROM выделял ей определенный кусок памяти, но в нокоторых случаях не всегда освобождал его. Например тогда, когда функция RSA отваливалась из-за невозможности расшифровать инвалидный блок, который я использовал в своём SPL в этой попытке. Что же получалось? ROM выделял память, пробовал расшифровать мою поддельную сигнатуру, забывал освободить память, выдавал ошибку CHECK: FFFFBF00 и цикл повторялся. В один прекрасный момент очередной кусок новой памяти затерал переменную из фьюза, которую ROM сохранял в самом начале. В следующем цикле в этой переменной уже было значение 0, что значило: просто грузи всё что есть и запускай.
Дальше в принципе не было ничего интересного. Я переделал оригинальный U-Boot, убрал проверку TPL, ядра и вернул ему обратно дар речи, чтобы он реагировал на команды. Расшифровав ядро и собрав минималку на BusyBox я запустил линукс из переделанного U-Boot’а, который начинал грузиться через 40 секунд.