Реверс-інжиніринг прошивки китайського Android-планшета

    
 
У китайців особливе уявлення про копірайт — у них він просто не діє. У той же час свої напрацювання вони захищають різними технічними засобами, чомусь «забуваючи» ділитися ними зі своїми клієнтами. Здавалося б, ситуація безвихідна: надійшла партія китайських планшетів, і встала задача прошити їх таким чином, щоб контент замовника не стирався при скиданні налаштувань, при цьому мається стоковая прошивка в невідомому bin-форматі, але відсутній SDK. Що ж робити, як зібрати кастомную прошивку? Вихід один — застосувати реверс-інжиніринг.
 
 
 Розвідка
Пристрій, з яким належало попрацювати, було побудовано на базі GeneralPlus GP330xx SoC , а його системне ПО розроблено за допомогою OpenPlatform SDK, і, хоча китайці заявляють про готовність надати вихідні коди, вони цього не роблять. Незважаючи на складність поставленого завдання, оптимізму додавав включений у пристрої за замовчуванням рутовий доступ. Тому процес вивчення розпочався з запуску ADB Shell.
 
Всі дисковий простір планшета представляло собою одне велике блоковий пристрій NAND-флеш (
/dev/block/nanda
), побите на розділи:
 
 
Disk /dev/block/nanda: 7457 MB, 7457472512 bytes
4 heads, 16 sectors/track, 227584 cylinders
Units = cylinders of 64 * 512 = 32768 bytes

Device Boot                Start         End      Blocks  Id System
/dev/block/nanda1             257      174335     5570528   b Win95 FAT32
/dev/block/nanda2          174336      207103     1048576  83 Linux
/dev/block/nanda3          207104      223487      524288  83 Linux
/dev/block/nanda4          223488      227583      131072  83 Linux

Частина пам'яті була виділена під так звану Internal SD card. Потрібно зупинитися на цьому терміні докладніше. В Android кожна прикладна програма запускається в своїй пісочниці і використовує для доступу до файлів системний API. Цей API дозволяє звертатися до внутрішньої пам'яті (Internal Storage) і зовнішньої пам'яті (External Storage). При цьому зовнішня пам'ять ділиться на removable storage media (SD-карта, яка вставляється в слот на торці пристрою) і internal (non-removable) storage (розділ внутрішньої пам'яті, «мімікрують» під SD-карту). В даному планшеті саме під внутрішню SD-карту було відведено найбільший розділ — / dev / block / nanda1. Тому його вирішено було розбити на два розділи, виділивши один з них під контент замовника, а другий — під внутрішню SD-карту.
 
Пристрій / dev / block / nanda розмічене за допомогою MBR, а не GPT, тому максимальна кількість primary розділів дорівнює чотирьом. За допомогою fdisk був видалений розділ / dev / block / nanda1, і на його місці створено extended-розділ з двома підрозділами / dev / block / nanda5 та / dev / block / nanda6.
 
 
 чаклуємо над розділами
Переглядаючи список змонтованих пристроїв, бачимо, що розділ / dev / block / vold / 253: 97 змонтований на / mnt / sdcard.
 
 
root@android:/etc # mount
...
/dev/block/vold/253:97 /mnt/sdcard vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1000,gid=1015,fmask=0602,dmask=0602,allow_utime=0020,codepage=cp437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
/dev/block/vold/253:97 /mnt/secure/asec vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1000,gid=1015,fmask=0602,dmask=0602,allow_utime=0020,codepage=cp437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
...

Який зв'язок між / dev / block / vold / 253: 97 і / dev / block / nanda1? Vold — це Volume Management daemon, демон монтування зовнішніх носіїв. У нього мається конфігураційний файл, по синтаксису схожий на стандартний ніксовий fstab, під назвою vold.fstab:
 
 
## Vold 2.0 Generic fstab
...
dev_mount sdcard /mnt/sdcard auto /devices/virtual/block/nanda /devices/virtual/block/nanda/nanda1 /devices/virtual/block/nanda/nanda2 /devices/virtual/block/nanda/nanda3 /devices/virtual/block/nanda/nanda4
...

На перший погляд все зрозуміло: / mnt / sdcard — це шлях монтування, auto — автоматичний вибір першого підходящого для монтування розділу зі списку розділів, зазначених далі (/ devices / virtual / ...). Однак файл vold.fstab в цьому пристрої був, по суті, «заглушкою». При внесенні модифікацій в рядок dev_mount sdcard… (наприклад, подмонтировать свіжостворений розділ, відмінний від / devices / virtual / block / nanda / nanda1), демон відмовлявся працювати. Важко сказати напевно, чи пов'язано це з кастомізованих ядром або ж з кастомізованих демоном, але, як би там не було, мотиви розробників такого рішення не ясні.
 
Таким чином, виявилося, що ні / dev / block / nanda5, ні / dev / block / nanda6 неможливо подмонтировать за допомогою vold. Далі можна було піти двома шляхами:
 
     
  1. Запускати монтування SD-карти з init-скриптів вручну. Правда, цей шлях не міг гарантувати 100% -й сумісності з усіма Android internals, іншими словами, не можна було б поручитися за стабільність роботи системи, прибравши з неї ключовий компонент «спілкування» із зовнішніми накопичувачами — vold.
  2.  
  3. Взяти відкриті вихідні vold і спробувати зібрати його для даного пристрою. Гарантій також ніяких, крім того, це могло б зажадати неабиякої кількості часу, якого, як завжди, не вистачало.
  4.  
Для такого методу розв'язання задачі довелося б писати shell-скрипти, що викликаються через ADB, і отримати результуючий бінарник прошивки ніяк би не вийшло, а це, в свою чергу, здорожило б роботу технічних фахівців замовника, так що цей шлях був залишений про запас і дослідження продовжилося в новому напрямку.
 
 
 Рішення прийшло раптово
Зіткнувшись з такою проблемою, я вирішив ще раз уважно вивчити те, що було у нас в руках. Особливий інтерес викликав прошівальщік, який, крім самого файлу прошивки firmware.bin, містив ще ряд допоміжних ресурсів: bootheader.bin, bootpack.bin, bootresource.bin, scanram.bin, updater.bin. Вони також необхідні, але нерелевантні для нашого завдання. Більший інтерес представляють файли, які використовуються прошівальщік для завантаження свого власного коду на пристрій: small_isp.bin, cmdline, initrd і kernel.
 
Даний пристрій використовувало для прошивки так званий ISP mode (це позначення одного з режимів програмування флеш-пам'яті). Алгоритм роботи прошівальщіка можна умовно розділити на чотири етапи:
 
     
  1. Технічний фахівець перезавантажує пристрій в режимі прошивки, затиснувши при його включенні кнопки & lt; Home + Power & gt ;.
  2.  
  3. прошівальщік пізнає пристрій по USB і перезавантажує його в ISP mode.
  4.  
  5. прошівальщік завантажує на пристрій Linux, передаючи файли cmdline, initrd і kernel.
    Файл kernel — це ядро ​​ОС, initrd — розділ з ПЗ прошівальщіка на стороні пристрою, cmdline — параметри ядра, що містять в собі розмір файлу initrd.
  6.  
  7. Завантажена на пристрої Linux починає приймати від прошівальщіка основні файли прошивки, розпаковувати їх і записувати відповідно до внутрішніх алгоритмами.
  8.  
Що ж являли собою ці внутрішні алгоритми? Рішення прийшло раптово. Виявилося, що initrd містив в собі вихідні коди прошівальщіка на мові Lua, а також бінарники додаткових Lua-модулів. Для розпакування initrd необхідно виконати наступні команди:
 
 
# mkdir initrd-unpacked
# cd initrd-unpacked
# gunzip < ../initrd | cpio -i --make-directories

Для зворотної упаковки (при необхідності; наприклад, для тестування модифікованих версій скриптів):
 
 
# find ./ | cpio -H newc -o > initrd.cpio
# gzip initrd.cpio
# mv initrd.cpio.gz initrd

Це може здатися дивним, але дійсно розробники навіщось придумали свій власний формат прошивки, при цьому залишивши скрипти, які оперують з цим форматом, в initrd у відкритому вигляді.
 
 Ð Ð¸Ñ. 1. Формат прошивки планшета
 
 
 Pluto
Хідер прошивки планшета були запаковані за допомогою модуля Pluto , який упаковує Lua-таблиці в бінарний формат. Мова програмування Lua взагалі активно використовує модулі, що підключаються, що представляють собою so-бібліотеки, які додають ті чи інші API. В добавок до всього, як випливало з документації, Pluto був платформо- і архітектурнозавісім. Intel і ARM (на якій був побудований планшет) істотно відрізняються: Intel використовує little-endian порядок байт в уявленні чисел, а ARM — big-endian.
 
І тут виникла серйозна проблема: стандартний модуль Pluto НЕ розпаковувати отримані дані. Були випробувані різні версії Lua і навіть різні архітектури CPU (x86, x86_64, ARM). Виявилося, що просто розробники прошивки використовували свою, ні з чим ні сумісну версію Pluto.
 
Для того щоб розпакувати дані, довелося скористатися емулятором QEMU для архітектури ARM і встановити на нього Debian Linux. А потім встановити Lua і покласти модуль pluto.so, витягнутий з initrd, в каталог модулів Lua.
 
 Ð Ð¸Ñ. 2. Заголовки бинарника прошивки в консоли ARM-эмулятора
 
 
Підключення Lua-модулів
Мова програмування Lua розширюється за рахунок зовнішніх підключаються модулів, які можуть бути написані як на Lua, так і на C. В останньому випадку це звичайні so-бібліотеки, що експортують ряд API-функцій.
Їх підключення проводиться за допомогою функції
require
, а за шлях пошуку бінарних модулів відповідає змінна
package.cpath
. У пропрієтарного LZO-модуля є своя особливість підключення, яка полягає в його найменуванні —
lua_lzo.so
. При цьому сам модуль називається lzo, через що його підключення замість звичного:
 
 
package.cpath = package.cpath .. "/home/mikhail/lua_so/?.so
require ;lzo;

слід виробляти так:
 
 
package.cpath = package.cpath .. /home/mikhail/lua_so/lua_?.so
require lzo

Також варто звернути увагу на пакетний менеджер LuaRocks, який дозволяє встановлювати модулі з єдиного сховища та зручно їх підключати. Наприклад, в рамках даного дослідження модулі nixio і MD5 були підключені саме через LuaRocks.
 
 
 LZO
Окрему складність підніс також алгоритм стиснення LZO. Справа в тому, що формат даних для цього алгоритму архівації не стандартизований, тому складно написати распаковщик, не знаючи, яким чином файл був запакований. Однак серед Lua-модулів initrd був модуль lua_lzo.so. На допомогу прийшов метод, описаний в попередньому абзаці, правда, ускладнений тим, що lua_lzo.so вимагав залежно системну бібліотеку liblzo.so (яка була взята з того ж initrd) і нестандартне підключення модуля через package.cpath.
 
Алгоритм стиснення LZO
LZO — сімейство блокових алгоритмів стиснення, що володіють важливими для портативних комп'ютерів характеристиками:
 
     
  • дуже високою швидкістю розпакування;
  •  
  • малим споживанням пам'яті;
  •  
  • поблочної розпакуванням даних, порціями невеликого розміру.
  •  
З точки зору реверс-інжинірингу він має два недоліки:
 
     
  1. LZO включає в себе дев'ять алгоритмів стиснення, і до кожного з них йде свій распаковщик.
  2.  
  3. Структура файлів LZO-архівів не стандартизовані, різні бібліотеки генерують різну структуру.
  4.  
В нашому випадку архівні дані мали наступний формат:
 
     
  1. Magic-послідовність («PMOC»).
  2.  
  3. Розмір блоку даних, використовуваний при упаковці (131072). Нагадаю, що в ARM використовується система little-endian, а значить, що це число відповідає hex-значенню 0x00000200 (см. Рис. 3).
  4.  
  5. Блоки даних, що містять:
     
       
    1. Розмір блоку (наприклад, 1816).
    2.  
    3. Запаковані дані позначеного вище розміру.

    4.  
     
  6.  
Це означає, що блок запакованих даних розміром 1816 байт розпакується в 128 кілобайт інформації.
 
Розпакування виконується в циклі, блоками даних. Для розпакування використовуються функції:
 
     
  1. handle = >lzo.decompressInit(header)
    , де header — magic number + розмір блоку архівації, handle — хендл, що використовується в двох інших функціях
  2.  
  3. ... = lzo.decompressPorcess(handle)
  4.  
  5. lzo.decompressFinish(handle)
  6.  
Примітно те, що необхідно точно знати розмір архіву, щоб розпакування виконалася успішно. В іншому випадку розпакування зависає на статусі
DECOMPRESS_NEED_MORE_DATA
. Розмір архіву вказаний в заголовку 2 (см. Рис. 1).
Компресія даних виконується складніше, тому що функції компресії не задокументовані і їх працездатність виявлялася пробним шляхом. Функції аналогічні:
 
     
  1. handle, header= lzo.compressInit(blockSize)
  2.  
  3. ...= lzo.compressProcess(handle, data)
  4.  
  5. lzo.compressFinish(handle)
  6.  
Відмітний момент компресії від декомпресії в тому, що перед записом блоку даних, отриманих в результаті виконання функції lzo.compressProcess, необхідно записати розмір упакованого блоку даних. Це випливає з загальної документації на алгоритм стиснення LZO і з аналізу архіву, отриманого при розборі оригінальної прошивки.
У підсумку, досліджуючи вихідний код скриптів, намагаючись зрозуміти їх логіку роботи, формати даних, а також провівши безліч експериментів, прошивку вдалося розпакувати.
 
 Ð Ð¸Ñ. 3. Размер блока данных LZO-архива
 
 
 ресайз системного розділу
Розпакований файл системного розділу (
system.bin
) являв собою образ файлової системи ext4. Для того щоб записати дані замовника, його необхідно було розширити на 1 Гб. Для цього потрібно зробити наступне:
 
     
  1. Розширити саму файлову систему.
  2.  
  3. В заголовку 2, в таблиці розділів, зменшити на 1 Гб розділ
    nanda1
    і збільшити на стільки ж розділ
    nanda2
    .
  4.  
  5. Знову заархівувати
    system.bin
    , перерахувати контрольні суми і записати їх в заголовки.
  6.  
Сам же ресайз системного розділу виконується наступними командами:
 
 
# mkdir system_new
# losetup /dev/loop0 system.bin
# e2fsck -f /dev/loop0
# resize2fs /dev/loop0 2G
# mount /dev/loop0 system_new
...
# umount system_new
# losetup -d /dev/loop0

 
 Робота з data-розділом В рамках вирішення даного завдання частина змін в системі вироблялася не тільки в
/system
, але і в
/data
. Для цього необхідно було розпакувати
dataImage.tar.gz
, зробити необхідні зміни і запакувати назад. Подібним чином слід вчинити і з
userImage.tar.gz
, якщо потрібно внести зміни в контент SD-карти.
Для упаковки зі збереженням прав доступу використовуємо наступні команди:
 
 
# tar cvf - . | gzip -9 - > ../user.tar.gz
# tar cvfp - . | gzip -9 - > ../data.tar.gz

 
 Заміна додатків за замовчуванням Замовнику було потрібно не тільки записати свій контент в постійну пам'ять пристрою, але і замінити стандартний launcher своїм власним додатком, забезпечивши необхідний User Experience.
Заміна launcher'а (і інших додатків за замовчуванням) проводилася шляхом редагування файлів /data/system/packages.list і /data/system/packages.xml. Спочатку дефолтні настройки виконувалися на пристрої, потім зміст файлів частково переносилося в прошивку.
Файл packages.list являє собою список встановлених в системі пакетів. Потрібний пакет launcher'а називається com.soaw.launcher і додається рядком:
 
 
com.soaw.launcher 10068 1 /data/data/com.soaw.launcher

А & lt; packages.xml — це база даних встановлених в системі пакетів і їх метаданих, таких як сертифікати, права доступу, програми за замовчуванням та інше. За налаштування програм за умовчанням відповідають два записи. Перший запис — це метадані launcher'а. Зверни увагу на атрибут index в тезі cert, його значення має бути на одиницю більше вже існуючого в файлі, щоб не трапилося плутанини сертифікатів.
 
 
package name="com.soaw.launcher" codePath="/system/app/SOAWLauncher.apk" nativeLibraryPath="/data/data/com.soaw.launcher/lib" flags="1" ft="141c2c2bbe0" it="141c2c2bbe0" ut="141c2c2bbe0" version="1" userId="10068"
sigs count=1;
cert index=20; key=... /sigs/package

Наступний запис — це настройки програм за замовчуванням. Тут задається вибір launcher'а і програми-медіаплеєра.
 
 
<preferred-activities>
  <item name="com.soaw.launcher/.activity.HomeActivity" match="100000" set="2">
    <set name="com.android.launcher/com.android.launcher2.Launcher"/>
    <set name="com.soaw.launcher/.activity.HomeActivity"/>
    <filter>
      <action name="android.intent.action.MAIN"/>
      <cat name="android.intent.category.HOME"/>
      <cat name="android.intent.category.DEFAULT"/>
    </filter>
  </item>
  <item name="com.android.gallery3d/.app.MovieActivity" match="600000" set="2">
    <set name="com.generalplus.GaGaPlayer/.MoviePlayerActivity"/>
    <set name="com.android.gallery3d/.app.MovieActivity"/>
    <filter>
      <action name="android.intent.action.VIEW"/>
      <cat name="android.intent.category.DEFAULT"/>
      <type name="video/mp4"/>
    </filter>
  </item>
</preferred-activities>

 
 Системні настройки
Як відомо, Android має SQLite базу даних системних налаштувань, яку можливо модифікувати на етапі підготовки образу прошивки. Файл бази даних знаходиться в /data/data/com.android.providers.settings/databases/settings.db.
Приховування нижній панелі виконується в таблиці system такими записами:
 
 
navigation_bar_mode = 4
navigation_bar_buttons_show = 0
navigation_bar_buttons_need_show = 0

Відключення екрану блокування виконується в таблиці secure:
 
 
coockscreen.disabled = 1

 Init-скрипти
Init-скрипти Android записуються на
/
в момент завантаження пристрою і тому, хоча вони і можуть бути відредаговані безпосередньо на пристрої, після перезавантаження сторінки будуть перезаписані оригінальними файлами. Найімовірніше, вони розташовуються в initrd, але дослідження на цю тему не проводилися.
 
 Висновок
Наше життя — процес. Закриті програмні системи — темний ліс. Процес пізнання потемок і є реверс-інжиніринг. Цей підхід допоміг не тільки вирішити основну бізнес-задачу — випустити кастомную прошивку, але і дізнатися більше про внутрішній устрій Android в цілому, що, безсумнівно, вельми цікаво для справжнього хакера. Важливо пам'ятати, що реверс-інжиніринг — інструмент легальний і універсальний. Не будь його, світ ніколи б не дізнався про найнебезпечніших Бекдор в прошивках провідних виробників мережевого устаткування, про апаратні «закладках» в мікропроцесорах, про витоки даних в популярних інтернет-додатках. Якщо хтось винайшов «чорний ящик», то завжди знайдеться той, хто зможе зрозуміти, як він працює.
 
Автор: Міхаіл Емельченков
 
 
 Вперше опубліковано в журналі «Хакер » від 02/2014.
 
Підпишись на «Хакер»
  
 
 
    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.