Категории

Cуществуют следующие способы оплаты за занятия:

  • Абонемент на 8 посещений (срок действия 1 месяц) - 300 грн.;
  • Абонемент на 4 посещения (срок действия 1 месяц) - 200 грн.;
  • Абонемент на 12 посещений(срок действия 1 месяц) - 400 грн.;
  • Разовое посещение - 60 грн.
(ДЛИТЕЛЬНОСТЬ ЗАНЯТИЙ ПО 1,5 ЧАСА)

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

  1. Підключення Lua-модулів
  2. Алгоритм стиснення LZO

Наше деловое партнерство www.banwar.org

У китайців особливе уявлення про копірайт - у них він просто не діє. У той же час свої напрацювання вони захищають різними технічними засобами, чомусь «забуваючи» ділитися ними зі своїми клієнтами. Здавалося б, ситуація безвихідна: надійшла партія китайських планшетів, і постало завдання прошити їх таким чином, щоб контент замовника не стирався при скиданні налаштувань, при цьому мається стічна прошивка в невідомому 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 174 335 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. Взяти відкриті вихідні коди vold і спробувати зібрати його для даного пристрою. Гарантій також ніяких, крім того, це могло б зажадати неабиякої кількості часу, якого, як завжди, не вистачало.

Для такого методу розв'язання задачі довелося б писати shell-скрипти, що викликаються через ADB, і отримати результуючий бінарник прошивки ніяк би не вийшло, а це, в свою чергу, здорожило б роботу технічних фахівців замовника, так що цей шлях був залишений про запас і дослідження продовжилося в новому напрямку.

Зіткнувшись з такою проблемою, я вирішив ще раз уважно вивчити те, що було у нас в руках. Особливий інтерес викликав прошивальщик, який, крім самого файлу прошівкіfirmware.bin, містив ще ряд допоміжних ресурсів: bootheader.bin, bootpack.bin, bootresource.bin, scanram.bin, updater.bin. Вони також необхідні, але нерелевантні для нашої задачі. Більший інтерес представляють файли, які використовуються прошівальщік для завантаження свого власного коду на пристрій: small_isp.bin, cmdline, initrd і kernel.

Цей пристрій використовувало для прошивки так званий ISP mode (це позначення одного з режимів програмування флеш-пам'яті). Алгоритм роботи прошівальщіка можна умовно розділити на чотири етапи:

  1. Технічний фахівець перезавантажує пристрій в режимі прошивки, затиснувши при його включенні кнопки <Home + Power>.
  2. Прошівальщік пізнає пристрій по USB і перезавантажує його в ISP mode.
  3. Прошівальщік завантажує на пристрій Linux, передаючи файли cmdline, initrd і kernel.
    Файл kernel - це ядро ​​ОС, initrd - розділ з ПО прошівальщіка на стороні пристрою, cmdline - параметри ядра, що містять в собі розмір файлу initrd.
  4. Завантажена на пристрої Linux починає приймати від прошівальщіка основні файли прошивки, розпаковувати їх і записувати відповідно до внутрішніх алгоритмами.

Що й казати виглядали ці внутрішні алгоритми? Рішення прийшло раптово. Виявилося, що 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 у відкритому вигляді.

Це може здатися дивним, але дійсно розробники чомусь придумали свій власний формат прошивки, при цьому залишивши скрипти, які оперують з цим форматом, в initrd у відкритому вигляді

Мал. 1. Формат прошивки планшета

Хідер прошивки планшета були запаковані за допомогою модуля Pluto , Який упаковує Lua-таблиці в бінарний формат. Мова програмування Lua взагалі активно використовує модулі, що представляють собою so-бібліотеки, які додають ті чи інші API. На додаток до всього, як випливало з документації, Pluto був платформо- і архітектурнозавісім. Intel і ARM (на якій був побудований планшет) істотно відрізняються: Intel використовує little-endian порядок байт в поданні чисел, а ARM - big-endian.

Підключення Lua-модулів

Мова програмування Lua розширюється за рахунок зовнішніх модулів, які можуть бути написані як на Lua, так і на C. В останньому випадку це звичайні so-бібліотеки, що експортують ряд API-функцій.

Їх підключення проводиться за допомогою функції MARKDOWN_HASHf0ffd3b7c2574ac324603ed00488c850MARKDOWN_HASH, а за шлях пошуку бінарних модулів відповідає змінна MARKDOWN_HASH97ba860ace5ebc5b33b41b35875c5412MARKDOWN_HASH. У пропрієтарного LZO-модуля є своя особливість підключення, яка полягає в його найменуванні - MARKDOWN_HASH93b13e5c63d9434db91396881bb35371MARKDOWN_HASH. При цьому сам модуль називається 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.

І тут виникла серйозна проблема: стандартний модуль Pluto НЕ розпаковувати отримані дані. Були випробувані різні версії Lua і навіть різні архітектури CPU (x86, x86_64, ARM). Виявилося, що просто розробники прошивки використовували свою, ні з чим ні сумісну версію Pluto.

Для того щоб розпакувати дані, довелося скористатися емулятором QEMU для архітектури ARM і встановити на нього Debian Linux. А потім встановити Lua і покласти модуль pluto.so, витягнутий з initrd, в каталог модулів Lua.

Мал. 2. Заголовки бінарники прошивки в консолі ARM-емулятора

Окрему складність підніс також алгоритм стиснення LZO. Справа в тому, що формат даних для цього алгоритму архівації не стандартизований, тому складно написати распаковщик, не знаючи, яким чином файл був запакований. Однак серед Lua-модулів initrd був модуль lua_lzo.so. На допомогу прийшов метод, описаний в попередньому абзаці, правда, ускладнений тим, що lua_lzo.so вимагав залежно системну бібліотеку liblzo.so (яка була взята з того ж initrd) і нестандартне підключення модуля черезpackage.cpath.

Розпакування виконується в циклі, блоками даних. Для розпакування використовуються функції:

  1. handle = lzo.decompressInit (header), де header - magic number + розмір блоку архівації, а handle - хендл, що використовується в двох інших функціях
  2. ... = lzo.decompressPorcess (handle)
  3. lzo.decompressFinish (handle)

Примітно те, що необхідно точно знати розмір архіву, щоб розпакування виконалася успішно. В іншому випадку розпакування зависає на статусеDECOMPRESS_NEED_MORE_DATA. Розмір архіву вказано в заголовку 2 (див. Рис. 1).

Компресія даних виконується складніше, так як функції компресії не задокументовані і їх працездатність виявлялася пробним шляхом. Функції аналогічні:

  1. handle, header = lzo.compressInit (blockSize)
  2. ... = lzo.compressProcess (handle, data)
  3. lzo.compressFinish (handle)

Відмітна момент компресії від декомпресії в тому, що перед записом блоку даних, отриманих в результаті виконання функції lzo.compressProcess, необхідно записати розмір упакованого блоку даних. Це випливає із загальної документації на алгоритм стиснення LZO і з аналізу архіву, отриманого при розборі оригінальної прошивки.

У підсумку, досліджуючи вихідний код скриптів, намагаючись зрозуміти їхню логіку роботи, формати даних, а також провівши безліч експериментів, прошивку вдалося розпакувати.

Мал. 3. Розмір блоку даних LZO-архіву

Розпакований файл системного розділу (system.bin) являв собою образ файлової системи ext4. Для того щоб записати дані замовника, його необхідно було розширити на 1 Гб. Для цього потрібно зробити наступне:

  1. Розширити саму файлову систему.
  2. У заголовку 2, в таблиці розділів, зменшити на 1 Гб розділ nanda1 і збільшити на стільки ж розділ nanda2.
  3. Знову заархівувати system.bin, перерахувати контрольні суми і записати їх в заголовки.

Сам же ресайз системного розділу виконується наступними командами:

# 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

В рамках вирішення даного завдання частина змін в системі проводилася не тільки в / 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

А 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:

lockscreen.disabled = 1

Init-скрипти Android записуються на / в момент завантаження пристрою і тому, хоча вони і можуть бути відредаговані безпосередньо на пристрої, після перезавантаження сторінки будуть перезаписані оригінальними файлами. Найімовірніше, вони розташовуються в initrd, але дослідження на цю тему не проводилися.

Алгоритм стиснення LZO

LZO - сімейство блокових алгоритмів стиснення, що володіють важливими для портативних комп'ютерів характеристиками:

  • дуже високою швидкістю розпакування;
  • малим споживанням пам'яті;
  • поблочної розпакуванням даних, порціями невеликого розміру.

З точки зору реверс-інжинірингу він має два недоліки:

  1. LZO включає в себе дев'ять алгоритмів стиснення, і до кожного з них йде свій распаковщик.
  2. Структура файлів LZO-архівів не стандартизовані, різні бібліотеки генерують різну структуру.

У нашому випадку архівні дані мали такий вигляд:

  1. Magic-послідовність ( «PMOC»).
  2. Розмір блоку даних, що використовується при упаковці (131072). Нагадаю, що в ARM використовується система little-endian, а значить, що це число відповідає hex-значенням 0x00000200 (див. Рис. 3).
  3. Блоки даних, що містять:
    1. Розмір блоку (наприклад, 1816).
    2. Запаковані дані позначеного вище розміру.

Це означає, що блок запакованих даних розміром 1816 байт розпакується в 128 кілобайт інформації.

Наше життя - процес. Закриті програмні системи - темний ліс. Процес пізнання потемок і є реверс-інжиніринг. Цей підхід допоміг не тільки вирішити основну бізнес-завдання - випустити кастомний прошивку, але і дізнатися більше про внутрішній устрій Android в цілому, що, безсумнівно, дуже цікаво для справжнього хакера. Важливо пам'ятати, що реверс-інжиніринг - інструмент легальний і універсальний. Без нього, світ ніколи б не дізнався про найнебезпечніших Бекдор в прошивках провідних виробників мережевого устаткування, про апаратні «закладках» в мікропроцесорах, про витоки даних в популярних інтернет-додатках. Якщо хтось винайшов «чорний ящик», то завжди знайдеться той, хто зможе зрозуміти, як він працює.

Що ж робити, як зібрати кастомний прошивку?
Який зв'язок між / dev / block / vold / 253: 97 і / dev / block / nanda1?
Що й казати виглядали ці внутрішні алгоритми?
Home/mikhail/lua_so/?
Home/mikhail/lua_so/lua_?