Ранее я писал, что приобрел за копейки материнскую плату Яндекс ТВ Станции с умершим звуком. Основная задача, которая была у данной платы - сдампить с ее помощью расшифрованное ядро Android, чтобы получить адреса функций, используемых в различных эксплоитах.
Дамп я сделал, но, в виду природной криворукости, случайно спалил на плате EMMC, задев проводом, припаянным к D0 какую-то площадку под чересчур высоким для EMMC напряжением. Тем самым материнская плата от ТВ Станции пополнила моё личное кладбище дохлых материнок и присоединилась там к глухой Яндекс Станции Макс, у которой я случайно сжёг Wi-Fi.
К слову, Яндекс (а, точнее, китайцы из Amlogic) не заморачиваются с закрытием уязвимостей, позволяющих получить права root, так что зная правильные адреса функций ядра Android не составит труда адаптировать существующий код с гитхаба под данное устройство.
Ссылки или номера CVE работающих эксплоитов я не приведу (кому надо - найдете сами), но немного полезной информации я опубликую.
1. Первое, о чем напишу, это KASLR.
Ядро Linux при каждой загрузке использует разные базовые адреса, чтобы запутать авторов эксплоитов. Но Яндекс тут опять позаботился о нас и выдает адреса на экран при загрузке:
Причем выдает не только виртуальные, но и физические адреса в памяти. А мой любимый эксплоит как раз предоставляет запись по физическим адресам.
На приведенном скриншоте мы видим, что начало ядра Linux в памяти (секция .text) начинается с 0xffffffe510080000 (виртуальный адрес) или 0x1080000 (физический). Физический адрес нам, правда, и так был известен - он тот же, что на Модуле 2 или Максе. Зато виртуальный адрес нам дает возможность пересчитать адреса под текущую базу ядра с учетом KASLR. Я это делаю так:
#define KERNEL_TEXT_BASE 0xffffffe510080000
#define COMMIT_CREDS_ADDR ((0xffffffc01013682c-0xFFFFFFC01009E000)+KERNEL_TEXT_BASE)
Здесь KERNEL_TEXT_BASE - текущий адрес начала ядра, 0xffffffc01013682c - адрес функции commit_creds, выдернутый из распакованного ядра Linux с помощью скрипта kallsyms_finder.py, а 0xFFFFFFC01009E000 - база сдампленного ядра. Ее можно определить как адрес самого первого символа, найденного kallsyms_finder.py (ffffffc01009e800 T do_undefinstr), округленного до начала страницы (то есть обнулив последние 3 цифры адреса).
2. Большинство эксплоитов первым делом отключают selinux.
Если посмотреть в UART, то мы видим довольно много сообщений "avc: denied"
В 5ом ядре Linux есть функция с похожим названием - avc_denied:
Как мы знаем, первый параметр функции передается в регистре X0, так что мы просто можем переделать в памяти ядра функцию avc_denied в такой код:
strb wzr, [x0]
mov x0, #0
ret
Данный код запишет 0 по адресу первого параметра, тем самым обнулив флаг включенного selinux для текущего процесса, который является первым элементом структуры selinux_state.
Код данных трех команд следующий:
0x3900001f, 0xd2800000, 0xd65f03c0
К слову, для преобразования ARM команд в 16-ричный код я использую https://armconverter.com.
Как видно на скриншоте, код команд он выдает побайтово, а не в формате little endian dword, но это не критично.
3. Изменение creds текущего процесса на root
Идея функции, которую следует пропатчить, чтобы получить права root, не моя, я ее подсмотрел в чьем-то эксплоите. Это функция sel_read_enforce, вызывающаяся при чтении файла "/sys/fs/selinux/enforce":
Если мы ее перезапишем своим кодом, и из своей программы или скрипта прочитаем содержимое "/sys/fs/selinux/enforce", то выполнится наш патч, который предоставит текущему процессу права root.
Ранее я писал, как эксплоиты превращают текущий процесс в процесс, с правами root:
commit_creds(prepare_kernel_cred(NULL));
Но у меня данный способ с первой попытки не заработал. Возможно, я ошибся в адресах, а, возможно, в этой версии ядра уже включен соответствующий фикс. Но зато заработал чуть более длинный код:
commit_creds(prepare_kernel_cred(pid_task(find_vpid(0), 0)));
Этот код можно использовать и в последних версиях ядра. Код взят отсюда.
Данный код предоставляет права не просто root, а права ядра Linux. Как негативный эффект, для ядра не выводятся сообщения "avc: denied", так что запатченная функция avc_denied не вызывается и selinux для него продолжает мешать. Но также и продолжает работать способ добавления исключений в selinux, описанный мной ранее. Всего лишь нужна команда:
sepolicy_inject -Z kernel -l
Так как я плохо знаю ассемблер ARM64, я пишу свой код на Си:
после чего компилирую его и смотрю что получилось:
aarch64-linux-android30-clang -Os -S ./patch.c
aarch64-linux-android-as ./patch.s
aarch64-linux-android-objdump -d ./a.out
Минус данного подхода - код требуется перекомпилировать каждый раз после перезагрузки из-за смены адресов KASLR, ведь все адреса функций в нем мной захардкожены.
Одной из приятных особенностей ARM процессора является генерация position independent кода по умолчанию. То есть, мы можем переделать приведенный выше код в PIC код просто поменяв константы адресов и команду BLR на BL на конкретный адрес, используя для расчета смещений базу пропатченной функции (ffffffc010532c44 t sel_read_enforce).
Я скормил код выше в https://grok.com, подсказал, что требуется поправить, вправил ему несколько раз мозги, так как он предложил откровенный бред, но по итогу получил исходник, который был скомпилирован с помощью armconverter.com и успешно заработал на моем ТВ:
Константы в коде:
ffffffc010532c44 - адрес sel_read_enforce, которую мы патчим
ffffffc01012f2a0 - адрес find_vpid
ffffffc01012f5c0 - адрес pid_task
ffffffc010136cb4 - prepare_kernel_cred
ffffffc01013682c - commit_creds
Таким образом, в начало sel_read_enforce я пишу следующие DWORDы:
static uint32_t root_code[] = {
0xD100C3FF, 0xA9027BFD, 0x910083FD, 0xB81FC3A0, 0xF9000BE1, 0xB9000FE2, 0xB9000BE3, 0xAA1F03E0, 0x97EFF18F, 0x2A1F03E1, 0xB90007E1, 0x97EFF254, 0x97F01010, 0x97F00EED, 0xB94007E0, 0xA9427BFD, 0x9100C3FF, 0xD65F03C0,
};
Так как данный код является position independent, то KASLR ему никак не мешает.
На всякий случай напишу очевидную вещь: данный код работает под конкретной версией ядра Android на Яндекс ТВ Станции:
yandex/magritte/magritte:11/RD2A.211001.002/2992354441:user/dev-keys
На любой другой версии адреса функций ядра будут другими, соответственно адреса в коде потребуется также исправить.
4. А теперь, собственно, ради чего всё это проделано
Как я писал, ADB включается путем установки qc_mode в 1 в разделе ENV. После получения прав root это можно сделать так (эксплоит и остальные команды я запускаю на ТВ через termux):
Хотя, имея права root, можно сделать намного больше, чем позволяет ADB. Но это уже совсем другая история.