Найти в Дзене
mamaich

Про эксплоиты на Яндекс ТВ Станции

Ранее я писал, что приобрел за копейки материнскую плату Яндекс ТВ Станции с умершим звуком. Основная задача, которая была у данной платы - сдампить с ее помощью расшифрованное ядро Android, чтобы получить адреса функций, используемых в различных эксплоитах. Дамп я сделал, но, в виду природной криворукости, случайно спалил на плате EMMC, задев проводом, припаянным к D0 какую-то площадку под чересчур высоким для EMMC напряжением. Тем самым материнская плата от ТВ Станции пополнила моё личное кладбище дохлых материнок и присоединилась там к глухой Яндекс Станции Макс, у которой я случайно сжёг Wi-Fi. К слову, Яндекс (а, точнее, китайцы из Amlogic) не заморачиваются с закрытием уязвимостей, позволяющих получить права root, так что зная правильные адреса функций ядра Android не составит труда адаптировать существующий код с гитхаба под данное устройство. Ссылки или номера CVE работающих эксплоитов я не приведу (кому надо - найдете сами), но немного полезной информации я опубликую. 1. Перв

Ранее я писал, что приобрел за копейки материнскую плату Яндекс ТВ Станции с умершим звуком. Основная задача, которая была у данной платы - сдампить с ее помощью расшифрованное ядро 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"

-2

В 5ом ядре Linux есть функция с похожим названием - avc_denied:

-3

Как мы знаем, первый параметр функции передается в регистре X0, так что мы просто можем переделать в памяти ядра функцию avc_denied в такой код:

strb wzr, [x0]
mov x0, #0
ret

Данный код запишет 0 по адресу первого параметра, тем самым обнулив флаг включенного selinux для текущего процесса, который является первым элементом структуры selinux_state.

Код данных трех команд следующий:

0x3900001f, 0xd2800000, 0xd65f03c0

К слову, для преобразования ARM команд в 16-ричный код я использую https://armconverter.com.

-4

Как видно на скриншоте, код команд он выдает побайтово, а не в формате little endian dword, но это не критично.

3. Изменение creds текущего процесса на root
Идея функции, которую следует пропатчить, чтобы получить права root, не моя, я ее подсмотрел в чьем-то эксплоите. Это функция sel_read_enforce, вызывающаяся при чтении файла "/sys/fs/selinux/enforce":

-5

Если мы ее перезапишем своим кодом, и из своей программы или скрипта прочитаем содержимое "/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, я пишу свой код на Си:

https://pastebin.com/aA7iHMPt
https://pastebin.com/aA7iHMPt

после чего компилирую его и смотрю что получилось:

aarch64-linux-android30-clang -Os -S ./patch.c
aarch64-linux-android-as ./patch.s
aarch64-linux-android-objdump -d ./a.out
-7

Минус данного подхода - код требуется перекомпилировать каждый раз после перезагрузки из-за смены адресов KASLR, ведь все адреса функций в нем мной захардкожены.

Одной из приятных особенностей ARM процессора является генерация position independent кода по умолчанию. То есть, мы можем переделать приведенный выше код в PIC код просто поменяв константы адресов и команду BLR на BL на конкретный адрес, используя для расчета смещений базу пропатченной функции (ffffffc010532c44 t sel_read_enforce).

Я скормил код выше в https://grok.com, подсказал, что требуется поправить, вправил ему несколько раз мозги, так как он предложил откровенный бред, но по итогу получил исходник, который был скомпилирован с помощью armconverter.com и успешно заработал на моем ТВ:

commit_creds(prepare_kernel_cred(pid_task(find_vpid(0), 0)));
commit_creds(prepare_kernel_cred(pid_task(find_vpid(0), 0)));

Константы в коде:

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

-9

Хотя, имея права root, можно сделать намного больше, чем позволяет ADB. Но это уже совсем другая история.