Volvo SCT / part 1 — как проникнуть в чужое ядро
Предисловие
Я являюсь обладателем одной интересной железки — SCT unit touch. Это медиа система с Android, которую ставили в виде дополнительного аксессуара в автомобили Volvo. Железка продавалась дистребьютерами Volvo, но разработана французским производителем электроники Parrot. По меркам Android, железка слабая, но по меркам встраиваемых систем вполне себе ок. Проблема в том, что ее оставили без поддержки как Volvo так и Parrot, cсобственно Гугл тоже давно убрал поддержку для столь старого Android. Из плюсов — железка не плохо интегрирована в машину и можно легко получить root. Из минусов — ни Parrot, ни Volvo не предоставляют ни исходники ядра ни SDK. В DevZone все давно потерто, поэтому придется допиливать напильником.
Спека
OS: Android 2.3.7 SDK:10 (API level — 10)
Processor: TI OMAP 3630 ARM Cortex-A8 800MHz (ARMv7 rev 2 v71)
Video: PowerVR SGX530 Resolution: 800×480 — 7»
Memory: 512MiB
Internal storage: 360MiB
System storage: 516MiB
System cache: 7,61MiB
2xUSB (max.64GiB FAT32)
1xSD-card (max.64GiB FAT32)
1xRCA input
Bluetooth
WI-FI 802.11 n
Внешний вид
Знакомство
Посмотрим, что за ядро нам досталось и с какими параметрами оно запускается. Начнем с uname и /proc/cmdline. Первое дает нам информацию о ядре, второе показывает с какими параметрами оно было запущенно. В параметрах есть первая улика — sgx_reduce_pixels_left интересный аргумент, специфичный для TI OMAP. Немного поисков в интернете выдают ссылку на чужой github репозиторий — asteroid_smart_kernel. Упомяну, что Asteroid это название серии продуктов Parrot, на котором базируется SCT, при этом используется чип от TI.
Первая удача — версия ядра в репозитории соответствует тому, что мы имеем на устройстве. К счастью, ядро на системе скомпилированно с опцией CONFIG_IKCONFIG_PROC — это дает возможность узнать у ядра конфигурацию с которой его компилировали. Поэтому копируем конфиг с которым компилировалось ядро из /proc/config.gz. Файл конфигурации задает диспозицию, т.е. он включает или выключает определенные функции ядра и решает, какой функционал пойдет в само ядро, а какой будет скомпилирован в виде модулей.
# uanme -r
2.6.35.13-02963-g0ac0fc3
# cat /proc/cmdline
androidboot.serialno=c5burjtir81tofqu5061 mtdparts=omap2-nand.0:512K(Pbootloader),16M(Pmain_boot),4M(Pfactory),1003M(Psystem),512K(Pmtdoops) console=null,115200 loglevel=0 ubi.mtd=Psystem,2048 ubi.mtd=Pfactory,2048 omap_vout.vid1_static_vrfb_alloc=y videoout=omap24xxvout omap_vout.video1_numbuffers=6 omapfb.vram=0:2M omapfb.mode=lcd:800x480MR-16@60 omaplfb.sgx_reduce_pixels_left=64 omaplfb.sgx_reduce_pixels_right=0 omaplfb.sgx_reduce_pixels_down=0 loglevel=0 androidboot.bootloader=ecos-bootloader-omap3630-start-58-gf2afe20
# adb pull /proc/config.gz
Дальше смотрим, по какому адресу ядро раскладывает свои внутренние символы. Для этого ищем в логах ядра начало инициализации. В данном случае — 0xc0108000 — 0xc0137000.
dmesg | grep init
<7>[ 0.000000] free_area_init_node: node 0, pgdat c06a60fc, node_mem_map c0792000<5>[ 0.000000]
.init : 0xc0108000 - 0xc0137000 ( 188 kB)
Вторая улика, которая нам поможет — ядро скомпилированно с CONFIG_KALLSYMS_ALL. Поэтому достаем внутренние символы ядра:
adb pull /proc/kallsyms
Судя по адресам, в kallsyms адреса соответствуют инициализации.
cat kallsyms
c0108000 T stext
c0108000 T _sinittext
c0108000 T _stext
c0108000 T __init_begin
c0108034 t __enable_mmu
c0108060 t __turn_mmu_on
c0108078 t __create_page_tables
c01080f0 t __switch_data
c0108118 t __mmap_switched
c0108160 t __error
c0108160 t __error_a
c0108160 t __error_p
c0108168 t __lookup_processor_type
Что это все значит и как с этим жить? Идем в исходники и берем уже знакомый нам по cmdline sgx_reduce_pixels_left. Это u32, т.е. 32 битный беззнаковый int. Описание говорит, что он урезает размер frame buffer с левой стороны.
В коде драйвера это выглядит подобным образом:
u32 sgx_reduce_pixels_left = CONFIG_SGX_XRES_REDUCE_PIXELS;
module_param(sgx_reduce_pixels_left, uint, S_IRUGO);
MODULE_PARM_DESC(sgx_reduce_pixels_left,"Reduce Android's size on framebuffer. Left");
EXPORT_SYMBOL_GPL(sgx_reduce_pixels_left);
В kallsyms это будет выглядеть таким образом:
cat kallsyms | grep sgx_reduce_pixels_left
c063b5b0 r __param_sgx_reduce_pixels_left
c0521b10 t __param_str_sgx_reduce_pixels_left
c061f810 r __ksymtab_sgx_reduce_pixels_left
c0625180 r __kcrctab_sgx_reduce_pixels_left
c0630bfe r __kstrtab_sgx_reduce_pixels_left
Формат: aдрес / тип / имя (префикс имеет значение)
T — глобальная функция
t — локальная функция модуля компиляции (static)
D — глобальные данные
d — локальные данные
R — глобальные read-only данные
r — локальные read-only данные
kstrtab — строковое имя символа
kcrctab — адрес CRC суммы каждого символа (будет разобрано дальше)
ksymtab — структура, которая описывает сам символ
Компиляция
Для начала определимся с компилятором, который использовал оригинальный автор.
# cat /proc/version
Linux version 2.6.35.13-02963-g0ac0fc3 (g-beguin@FR-B-800-0057) (gcc version 4.4.3 (GCC) ) #1 PREEMPT Mon Feb 10 11:02:21 CET 2014
GCC 4.4.3 можно взять тут. Интересно, что beguin — это имя пользователя. Нашел на Linkedin только одного embedded разработчика с такой фамилией, но он не признался, что это его рук дело =) Вторая теория более грустная и говорит, что beguin можно перевести с французского, как «увлечение».
Выкачиваем один рандомный модуль ядра с устройства, чтобы посмотреть на его внутренности:
adb pull /system/lib/modules/2.6.35.13-02963-g0ac0fc3/kernel/drivers/media/video/tvp5150.ko
/system/lib/modules/2.6.35.13-02963-g0ac0fc3/kernel/drivers/media/video/tvp5150.ko: 1 file pulled. 0.2 MB/s (27496 bytes in 0.162s)
что говорит modinfo:
modinfo ./tvp5150.ko
filename: ./tvp5150.ko
license: GPL
author: Mauro Carvalho Chehab
description: Texas Instruments TVP5150A video decoder driver
srcversion: A9FDE6A9EB55B0DBACD3892
alias: i2c:tvp5150
depends:
vermagic: 2.6.35.13-02963-g0ac0fc3 preempt mod_unload modversions ARMv7
parm: debug:Debug level (0-2) (int)
Самое интересное тут — vermagic. По сути это — виза для модуля, по которой ядро решает пускать его или нет. Если vermagic не сойдется, то ядро нас вышвырнет. К сожалению, хотя исходники у нас соответствует по версии, но у нас нет нужного коммита, т.к. кровавые корпорации не хотят делиться модифицированным GPL кодом. По скольку у нас есть конфиг — это частично решает проблему, но не до конца. Выкачиваем исходники ядра и. скармливаем ему наш конфиг.
git clone https://github.com/CunningLogic/asteroid_smart_kernel.git
dtrx config.gz
Теперь нам нужно подделать vermagic. Для этого выставим два параметра: включим логику билд системы для автоматического определения версии и выставим локальную версию на нужный нам коммит.
CONFIG_LOCALVERSION_AUTO=y
CONFIG_LOCALVERSION="-02963-g0ac0fc3"
Первый полёт
Создаем наш тестовый модуль (vkb.c).
#include
#include
static int __init prsyms_init(void)
{
printk(KERN_INFO "we are in!\n");
return 0;
}
static void __exit prsyms_exit(void)
{
}
module_init(prsyms_init);
module_exit(prsyms_exit);
MODULE_AUTHOR("incogn1to");
MODULE_DESCRIPTION("Get into unknown kernel");
MODULE_LICENSE("GPL");
Простенький мейк
obj-m += vkb.o
all:
make -C .. M=$(PWD) modules
clean:
make -C .. M=$(PWD) clean
При компиляции — выставляем архитектуру, версию и путь к компилятору. Почем так сложно? Это станет понятно немного дальше.
#!/bin/sh
export ARCH=arm
export CONFIG_LOCALVERSION="-02963-g0ac0fc3"
export CROSS_COMPILE=~/tools/arm-linux-gcc-4.4.3/bin/arm-none-linux-gnueabi-
make
Смотрим, что получилось:
modinfo vkb.ko
filename: /home/incogn1to/src/asteroid_smart_kernel/vkb/vkb.ko
license: GPL
description: Control over virtual buttons
author: incogn1to
srcversion: B73A9A163E53563A49DD2B5
depends: path
vermagic: 2.6.35.13-02963-g0ac0fc3+ preempt mod_unload modversions ARMv7
Vermagic почти такой, какой нам нужен, но есть лишний плюсик. Это работа скрипта, который называется setlocalversion. Этот скрипт распознал изменения в репозитории, которые не были закомичены в репозиторий и решил таким образом отметить этот факт. Придется отучить его это делать. Сделаем так, чтобы scm_version просто возвращала CONFIG_LOCALVERSION.
scm_version()
{
return ${CONFIG_LOCALVERSION};
}
Еще раз запускаем компиляцию и смотрим результат.
modinfo vkb.ko
filename: /home/incogn1to/src/asteroid_smart_kernel/vkb/vkb.ko
license: GPL
description: Control over virtual buttons
author: incogn1to
srcversion: B73A9A163E53563A49DD2B5
depends: path
vermagic: 2.6.35.13-02963-g0ac0fc3 preempt mod_unload modversions ARMv7
Теперь veramagic идентичен модулю взятому с системы и можно попробовать внедрить модуль в kernel space. Пробуем…
# insmod ./vkb.ko
insmod: init_module './vkb.ko' failed (Exec format error)
К сожалению что-то пошло не так. Но что…
dmesg | grep vkb
<4>[ 444.145111] vkb: no symbol version for module_layout
Хьюстон, у нас проблема…
Основная проблема прячется за включенным modversions. Это значит, что при компиляции, для каждого символа ядра вычисляется CRC и мы можем вызвать только те символы, CRC которых мы знаем. Если CRC не сходится — ядро отказывается от вызова. Что мы можем сделать? Взять CRC символов из модулей, которые есть на системе. Расковырять их нам поможет вот этот скрипт. В итоге получаем Module.symvers с известными нам CRC. Их смело можем использовать. Вызовы к другим символам будут отвергнуты ядром.
Натравливаем скрипт на доступные модули ядра и в итоге получаем новый Module.symvers.
0x56941536 tty_kref_put dummy/path
0xa4755a9a tty_port_tty_get dummy/path
0xdb3877d ___dma_single_dev_to_cpu dummy/path
0xa69430b5 mmc_card_sleep dummy/path
0x94af632e complete_all dummy/path
0xefef88d5 posix_lock_file_wait dummy/path
0xe55e144a proc_dointvec_minmax dummy/path
0xdd80f922 usb_wwan_dtr_rts dummy/path
0xaf5bf6ef nfs_debug dummy/path
0xf8ca57a9 nf_ct_l3proto_put dummy/path
0xc4ddd373 usbnet_get_ethernet_addr dummy/path
0xe0878bfe __krealloc dummy/path
0x8463fdda consume_skb dummy/path
0x98447920 sunrpc_cache_unregister_pipefs dummy/path
0x8e9e1a6c sock_sendmsg dummy/path
0xd03c7700 secure_ipv4_port_ephemeral dummy/path
0x20000329 simple_strtoul dummy/path
0x3d7690a1 crypto_spawn_tfm dummy/path
0x767002b6 snd_ctl_add dummy/path
0xb54533f7 usecs_to_jiffies dummy/path
0xa9a951a0 tty_hangup dummy/path
0x4336eda0 dev_driver_string dummy/path
0xfbe27a1c rb_first dummy/path
0x2882da10 rfkill_alloc dummy/path
0x2a14ddb8 ethtool_op_set_tx_hw_csum dummy/path
0x567e5b8 sdio_readsb dummy/path
Копируем наш новенький Module.symvers в корень ядра и запускаем компиляцию модуля. Теперь становится понятно, почему нам нужен такой странный способ компиляции. Задача — скомпилировать модуль относительно существующих symvers и config, так чтобы при этом система сборки ядра их не трогала и не пыталась пересчитать. Если все сделано правильно, то ядро примет наш модуль.
NB! Если symvers для символа не известен — модуль все равно скомпилируется, т.к. make просто выставит там 0×00, что будет отвергнуто ядром.
Заливаем его на устройство и скармливаем ядру c помощью insmod.
adb push vkb.ko /mnt/sdcard
vkb.ko: 1 file pushed. 0.2 MB/s (23834 bytes in 0.129s)
adb shell
cd /mnt/sdcard
insmod ./vkb.ko
dmesg
<3>[ 869.831817] init: untracked pid 4872 exited
<6>[ 1130.217773] we are in!
Репозиторий с моими изменениями доступен тут.
PS
Была part-0 с получением root, но я решил, что это уже сто раз было описано. Если кому-то будет интересно — могу описать.
Если у кого-то по абсолютной случайности сохранились исходники от этой железки — буду очень рад.