Патчим прошивку Android за 5 минут / Хабр

gist og image Новости

Извлекаем с устройства загрузочный образ

— Подключаем устройство к компьютеру через usb-кабель и заходим в shell устройства:

adb shell

— Где-то в папке

Затем загрузочный образ нужно разобрать

— Скачиваем и распаковываем bootimg_tools.

— Добавляем bootimg_tools в PATH:

export PATH=$PATH:/path/to/bootimg_tools

— Распаковываем образ:

unpack boot-from-device.img

— Если все предыдущие шаги сделаны верно, то должна появиться папка

boot-from-device

. Внутри нее должны лежать файлы

zImageramdisk.cpio.gz

, а так же папка

ramdisk

. Как раз внутри последней лежит содержимое загрузочного образа, которое мы будем патчить.

Патчим


Для решения своей задачи я нашел строчку, которую нужно заменить, в файле init.sun8i.rc. Я просто заменил

export EXTERNAL_STORAGE /mnt/sdcardexport EXTERNAL_STORAGE /mnt/extsd

. Как я понимаю, это как раз и есть то значение, которое возвращает метод

Environment.getExternalStorageDirectory()

в Android API. В пользу этого предположения говорит тот факт, что после применения патча приложения начали использовать аппаратную карту памяти вместо виртуальной для хранения своих данных.

Заменяем загрузочный образ на устройстве

Я написал для этих целей небольшой скрипт:

repack_ramdisk boot-from-device/ramdisk
mkbootimg --kernel boot-from-device/zImage --ramdisk boot-from-device/new-ramdisk.cpio.gz --base 0x40000000 --cmdline 'console=ttyS0,115200 rw init=/init loglevel=4' -o new_boot.img
sudo adb reboot-bootloader
sudo fastboot flash boot new_boot.img
sudo fastboot reboot

Dirtycow (cve-2021-5195)

Простыми словами dirtycow (рабочий эксплойт под Android) позволяет заменить память любого процесса (полезно, если хорошо знаешь ASM) или любой доступный для чтения файл, даже если он находится на readonly файловой системе. Желательно, чтобы подменяемый файл был меньше либо равен по размеру заменяемому.

Основная атака в dirtycow для Android — подмена /system/bin/run-as — подобие sudo для отладки приложений. Начиная с API android-19 (таблица соответствия версий API и Android) /system/bin/run-as имеет CAP_SETUID и CAP_SETGID capabilities флаги (в старых версиях используется обычный suid bit — 6755).

$ getcap bin/run-as 
bin/run-as = cap_setgid,cap_setuid ep

Если файловая система будет примонтирована в режиме read-write, то всё, что dirtycow подменяет, окажется на файловой системе. Потому необходимо сделать backup оригинального файла и восстановить его после получения доступа, либо не перемонтировать файловую систему в режиме read-write. Как правило раздел /system в Android по умолчанию примонтирован в режиме read-only.

Не зря dirtycow считается одной из серьезнейших уязвимостей, обнаруженных в Linux. И при наличии знаний с помощью dirtycow можно обойти все уровни защиты ядра, в том числе и SELinux.

Hooks

Kyocera долго не думала, а просто запилила хуки на потенциально опасные операции в Android: mount, umount, insmod (к загрузке разрешен всего один модуль — wlan и только если его загрузит init процесс), и прочее.

Вот где крылась проблема recovery. Он не мог отмонтировать файловую систему /system! Эти операции позволялись только init процессу. В том числе я не мог отключить SELinux потому что эта возможность была отключена при компиляции ядра. Обойти эти хуки можно было только, если ядро загружено с определенными параметрами (kcdroidboot.mode=f-ksg или androidboot.mode=kcfactory, о них чуть позже).

How to: modify boot.img to allow usb debugging from recovery adb

magisk workaround.

if you can’t patch your boot.img since you don’t have a RAMDISK,
you might want to install magisk. with it you can load stuff at the same stage of boot.

but the “systemless” installation of magisk sucks.

luckily if you already have a custom recovery, you pretty much void-ed your warranty already and triggered knox bit…

so you don’t have to work that hard to get magisk working on your device.

no need to patch of boot.img or recovery.img either!

download https://github.com/topjohnwu/Magisk/releases/download/v23.0/Magisk-v23.0.apk
(or new one from https://github.com/topjohnwu/Magisk/releases/latest).

make another copy of the file,

rename the copied file’s extension from “.apk” to “.zip”,

Magisk-v23.0.apk
Magisk-v23.0.zip

place both zip and apk files in your device,

boot into recovery and flash it like a zip,

clear cache and dalvik,

reboot to system.

this is called the ‘system-as-root’ mode,

and not a lot of people are aware of it. undocumentated.

either connect to the internet and click the magisk “stub” icon to download and install the magisk manager,

or simply run the apk you’ve downloaded and already have on your device, it’s the same.


there are not a lot good examples for magisk modules,

you might need to write your own module with a small shell script that runs atpost-fs-data stage.

then you can write a few setprop commands,

for example setprop ro.debuggable 1

or anything really from https://huaweidevices.ru/gregor160300/068c06c0314c19855e999473708c7635#file-default-prop,

there are some examples here and here,

once you’ve done, load your module to magisk, activate it and reboot.

you can try to open a terminal and use getprop to check the value of the property.

you may even help your fellow users by hosting your magisk-module on github, and submitting it to the Magisk-Modules-Repo.

Restart

Тоже интересный для изучения файл. В нем описываются возможные варианты загрузки телефона:

Root, да не тот

Первое, что я сделал это использовал dirtycow по прямому назначению — подменил /system/bin/run-as, который задал UID/GID в 0 (тоже самое делает su). Однако монтировать файловую систему я не мог, даже tmpfs. Загружать модули ядра я тоже не мог.

Просматривать dmesg — нет. Я даже не мог просматривать директории, которые имели права 700 и принадлежали другим системным пользователям. Я мог лишь читать и писать в блочные устройства, а просмотр файлов или директорий был возможен благодаря заданию UID/GID определенного пользователя (написал свой велосипед — аналог su, который мог задавать selinux context и пользователя/группу).

Первым делом я сделал дамп всей прошивки, boot и recovery:

Uevent_helper

В моём случае, на удивление, была возможность задать /sys/kernel/uevent_helper. Если в этот параметр записать путь до executable файла (shell script тоже сойдёт), то он с определенным интервалом будет запускаться от процесса init в контексте init и что самое важное с full capabilities.

Я написал скрипт:

Всем тем, кто неудачно прошил свой android-девайс

Всё-таки отключаем selinux

Из спортивного интереса я всё-таки решил отключить SELinux. Заменить defined значение selinux_enabled мы не можем, но мы можем разыменовать структуру с hooks функциями security_ops.

Это делается вызовом функции reset_security_ops:

void (*_reset_security_ops)(void) = NULL;
... ... ...
_reset_security_ops = (void (*)(void))kallsyms_lookup_name("reset_security_ops");
if (_reset_security_ops != NULL) {
  _reset_security_ops();
}

После этого защита SELinux работать не будет, но система всё еще будет думать, что она включена. Потому возможны некоторые ошибки в работе системы.

Загрузка fota

Почему boot раздел не грузится из раздела fota? Ведь они подписаны одним ключём. Если бы загрузка произошла, то я бы получил разлоченный телефон, в котором не пришлось бы подменять модули.

Задача

Все началось с того, что я захотел установить на планшет Digma Optima 7.61 игру GTA San Andreas. К сожалению, виртуальная SD-карта планшета имеет объем менее 1 Гб, а кэш игры весит порядка 2-3 Гб. На планшете установлен Android 4.4.2 и возможность просто взять и сменить память по умолчанию в нем отсутствует.

Копаем recovery

Проверяю разницу между boot и recovery разделами. Все идентично кроме initramfs. В initramfs раздела recovery изучаю init.rc, в котором описан лишь один сервис, который запускает /sbin/recovery. Изучаю strings sbin/recovery | less, затем исходники оригинального recovery.

Как видно, по умолчанию recovery просто отображает логотип Android. А если необходимо что-то сделать, то в штатном режиме в раздел /cache записывается файл /cache/recovery/command, который может содержать параметры запуска recovery. Если в этот файл записать –show_text то мы должны увидеть меню.

Запускаю dirtycow exploit, выставляю UID/GID, записываю файл и запускаю adb reboot recovery. Телефон перезагружается и я попадаю в меню стандартного recovery. Уже что-то. Пробую прошить ZIP файл с supersu через adb sideload.

Выясняю, что initramfs содержит публичный ключ res/keys в формате minicrypt, которым проверяется цифровая подпись ZIP файла. Оказалось это стандартный тестовый ключ Android, и что я могу подписать этим ключём любой архив. Проверить это можно следующим образом:

java -jar dumpkey.jar android/bootable/recovery/testdata/testkey.x509.pem > mykey
diff -u mykey res/keys

Попробовал установить ZIP напрямую с sdcard, но в recovery при монтировании sdcard возникала ошибка. Изучил etc/recovery.fstab, оказалось что в режиме recovery sdcard монтируется как vfat:

Копаем исходники ядра

Лицензия GPL обязывает производителей смартфонов выкладывать исходники ядра. Спасибо Линусу и Столлману за это. Иногда производители выкладывают что-то левое, иногда правильные исходники, но без defconfig файла, иногда правильные и очень редко с инструкцией как их собирать (например LG).

В моём случае были исходники с правильным defconfig но без инструкции. Немного попотев я смог собрать ядро и убедился, что это не полная липа.

Через продолжительное время я остановился на двух файлах:

Необходимые инструменты

1. Компьютер или ноутбук с ОС Linux.

2. Утилиты adb и fastboot (входят в состав Android SDK, так же, как заметил

, входят в репозитории как отдельные пакеты).

3. Набор утилит

(нашел

Патченный adbd

Т.к. мне надоело иметь доступ к урезанной консоли Android, а патчить бинарник adbd мне лень, то я решил собрать свой adbd

с блэкджеком и шлюхами

Перезагружаемся в download mode

int (*_enable_dload_mode)(char *str) = (int(*)()) 0xc0d0cc18;
... ... ...
_enable_dload_mode("dload_mode");

Та же операция работает и с download_mode, о котором я писал выше. После загрузки модуля телефон перезагружается в спец режим, который работает как usb mass storage device. Т.е. я имею доступ ко всем разделам телефона без защиты от чтения! Попробовал записать свой recovery.

Пришлось ограничить скорость записи, иначе телефон отваливается и запись прекращается. Возможно это результат переполнения кэша mass storage загрузчика. Пришлось написать хак:

Пробуем отключить selinux

На тот момент я думал, что все ошибки об отсутствии привилегий вызваны SELinux (я полностью забыл о том, что могут быть урезаны capabilities). Логов dmesg я не видел, logcat ничего релевантного не показывал. И я начал думать как отключить SELinux.

Первая зацепка, которую я смог найти:

$ grep -A2 reload_policy boot/ramfs/init.rc 
on property:selinux.reload_policy=1
    restart ueventd
    restart installd

Исходники говорят о том, что при изменении этой опции, init перезагружает политики SELinux из файла /sepolicy.

Т.е. я при помощи dirtycow могу перезаписать /sepolicy и командой setprop selinux.reload_policy 1 загрузить обновленную политику.

Для начала нужно выяснить что из себя представляет /sepolicy. Изучить его можно с помощью команды sesearch (пакет setools в Debian).

$ sesearch --allow sepolicy
$ sesearch --neverallow sepolicy
$ sesearch --auditallow sepolicy
$ sesearch --dontaudit sepolicy

В моём случае /sepolicy содержал только allow, что значит — при enforcing режиме SELinux в Android разрешено делать только то, что объявлено в политике. А процессу init разрешалось только загружать политику, но не отключать:

$ sesearch --allow sepolicy | grep 'load_policy'
   allow init kernel : security load_policy ;

Моей задачей было — разрешить init контексту задать selinux->enforce в permissive (setenforce 0).

Первое, что я сделал — собрал стандартную политику стокового Android, подменил оригинальный /sepolicy, загрузил (под root пользователем setprop selinux.reload_policy 1) и получил сообщение в статусной строке, что телефон находится в незащищенном режиме.

Первая же мысль: стоковая политика не подходит этому телефону и он при отсутствии прав начинает тупить.

Я собрал новую политику, в которой просто описал все существующие SELinux context и объявил их permissive. Тоже не помогло.

Потом я решил пересобрать политику и по возможности добавить привилегии для shell контекста.

Я нашел статью, в которой говорится как “декомпилировать” политику. Немного повозившись я смог собрать все зависимости и запустить утилиту sedump. На выходе я получил текстовый файл, который я смог собрать обратно (для KitKat checkpolicy -M -c 26 -o sepolicy.new policy.conf) и даже получить файл с точно таким же размером что и оригинальный sepolicy, но разным hex содержимым. Загрузка новой политики вызвала точно такие же результаты, что и ранее — телефон через какое-то время перезагружался.

Я решил собрать две политики: из policy.conf, который я получил и из policy.conf, в котором добавлены все привилегии для allow init kernel : security, в том числе и setenforce. Сравнить файлы в hex и по аналогии заменить байты в оригинальном sepolicy.

Как выяснилось две пересобранные политики отличались всего парой байт. Я начал искать похожие совпадения в оригинальном sepolicy, но не нашёл. Затем я просто написал brute force скрипт, который в заданном диапазоне смещений заменяет два байта на “0xFF,0xFF”, запускает sesearch –allow | grep “нужный результат”.

Чуть позже я нашел утилиту sepolicy-inject, которая добавляет привилегии в уже скомпилированный sepolicy файл. Если правило уже существует, то добавление максимальных привилегий не увеличивает конечный размер политики. К сожалению запуск утилиты добавляет всего одно правило за раз.

Потом я узнал, что в Android есть команда load_policy, которая перезагружает политику по любому пути. Танцы с бубном оказались лишними.

adb shell run-as /data/local/tmp/run -u system -c u:r:init:s0 load_policy /data/local/tmp/sepolicy.new

Можно было добавить любой permissive домен, загрузить новую политику и работать в контексте этого домена (кстати, supersu от chainfire для новых версий Android так и работает). Но даже это не дало возможности отключить SELinux. Я решил копать в другом направлении.

Расшифровка kyocera properties

Kyocera наряду с android system properties использует свой внутренний механизм properties, который мне тоже не удалось выяснить. Наверняка там хранятся интересные опции, которые могут влиять в том числе и на снятие защиты bootloader’а. В телефоне есть библиотека libkcjprop_jni.so и демон kcjprop_daemon. Библиотеку можно подключить и использовать её функции, но у меня пока не нашлось времени этого сделать.

Опции пишутся на файловую систему, внутри бинарные данные:

$ ls -la /sysprop/kcjprop/rw/8d9d788ddd5fecfdbc6c5f7c5cecfc    
-rw-rw---- root     root           16 1970-01-22 21:01 8d9d788ddd5fecfdbc6c5f7c5cecfc

Уязвимость в qsee

QSEE — защита в процессорах Qualcomm, в которой

недавно

уже достаточно давно

. Суть уязвимости — полный доступ к выполнению команд на уровне Trust Zone. Вплоть до загрузки любого ядра.

Цифровая подпись aboot и boot разделов

Я пытался выяснить каким публичным ключём подписаны boot образы. Распаковал ключи из aboot (binwalk -e aboot), извлек подписи из образов и прошелся всеми публичными ключами по ним. Выяснил, что все образы подписаны одни ключём. С ходу не разобрался как вычислить смещение подписи у boot разделов, потому я просто перепаковываю образ и использую его размер как смещение.

Эксперименты с загрузчиками

Для проведения экспериментов я заказал из штатов за символическую сумму Kyocera Brigadier с разбитым экраном.

Я проверил цифровые подписи aboot загрузчиков. Subject’ы сертификатов оказались идентичными, следовательно они могут быть взаимозаменяемыми. Решился на эксперимент: прошить aboot от KC-S701 в Brigadier. Загрузчик заработал. На удивление, защита emmc от записи с этим загрузчиком не включилась и я смог спокойно восстановить загрузчик от Brigadier.

Тут история могла бы закончиться, но “телефон не загрузился” — это черный экран. И на моё счастье это был черный экран не Qualcomm QHSUSB__BULK, который требует специального подписанного загрузчика для восстановления телефона, а тот самый download mode, который представляет телефон как USB mass storage. Телефон был восстановлен. На сегодняшний день это последнее, что я предпринял.

Итоги

После проделанных манипуляций игра на планшете успешно заработала.

Оцените статью
Huawei Devices
Добавить комментарий