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

Внешний вид

252580c431763b2bbf62c0c962964869.jpgae21715c6aee63fc4514f142bce50ae9.jpg93d3b44ae3260bcc10e3b97ac530bbc8.png

Знакомство

Посмотрим, что за ядро нам досталось и с какими параметрами оно запускается. Начнем с 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, но я решил, что это уже сто раз было описано. Если кому-то будет интересно — могу описать.

Если у кого-то по абсолютной случайности сохранились исходники от этой железки — буду очень рад.

Ссылки по теме:

© Habrahabr.ru