Как стать автором
Обновить
123.05
НТЦ Вулкан
Исследуем, разрабатываем, защищаем

В тихом омуте… или интересный режим работы смартфона OnePlus 6T

Уровень сложностиСредний
Время на прочтение11 мин
Количество просмотров7.3K

Несколько лет назад один из членов нашей команды заказал себе OnePlus 6T прямо из Китая. Телефон пришел в оригинальной упаковке и типовой комплектации: с зарядным устройством, кабелем и чехлом. Смартфон без проблем проработал год, ничем, на первый взгляд, не отличаясь от тех, что продаются в России.
Но однажды приложения начали предупреждать о наличии root-доступа, а некоторые, особенно банковские, вообще перестали запускаться. При этом прошивка никаким образом не модифицировалась, а обновления устанавливались исключительно из официальных источников, относящихся к ОС. Такое странное поведение смартфона побудило нас провести исследование, результаты которого описаны в этой статье.

alt text

0. Поиск причины

Какое-то время нам не удавалось понять, в чем причина такого поведения приложений. Устройство проходило проверку Play Integrity Strong, а также подтверждалась подлинность цепочки загрузки при проверке аттестации приложением Key Attestation.

alt text

Через некоторое время стало ясно, что причиной такого поведения стали установленные параметры ro.debuggable=1 и ro.adb.secure=0. Разобраться в этом оказалось довольно просто: после множества тщетных попыток понять, в чем проблема, мы попытались выполнить команду adb root. В результате adb shell запустился с правами root без каких-либо ошибок:

Демон adbd осуществляет проверку параметра ro.debuggable при сбросе привилегий с root до shell (без него невозможно подключиться клиентом, сохранив права root), а параметр ro.adb.secure влияет на необходимость аутентификации при подключении adb-клиента.

OnePlus6T:/ # getprop|grep ro.debug
[ro.debuggable]: [1]
OnePlus6T:/ # getprop|grep ro.adb
[ro.adb.secure]: [0]

Встал вопрос: как такое возможно?
Наличие параметра androidboot.verifiedbootstate=green в командной строке ядра свидетельствует о заблокированном с ключами производителя загрузчике, что в свою очередь исключает компрометацию посредством повторной блокировки устройства. Это подтверждается тем, что устройство успешно проходило проверку play integrity. В случае разблокировки параметр verifiedbootstate изменяется на orange (или yellow после повторной блокировки), и при включении устройства отображается соответствующее окно .

yellow_orange

При перезапуске устройства в режиме recovery, оно также предоставляло рутовый шел через adb – это и начинало наводить на определенные мысли.

1. Как прошивка с включенной возможностью отладки попала в release-сборку?

Для анализа ситуации с использованием root-доступа был получен лог загрузки устройства (/proc/bootloader_log). Наиболее интересным в нем оказался фрагмент, содержащий командную строку запуска ядра:

OemCmdLine
OemInfo.OemCmdLine len 805
Cmdline: androidboot.hardware=qcom androidboot.console=ttyMSM0 video=vfb:640x400,bpp=32,memsize=3072000 msm_rtb.filter=0x237 ehci-hcd.park=3 lpm_levels.sleep_
Cmdline: disabled=1 service_locator.enable=1 swiotlb=2048 androidboot.configfs=true loop.max_part=7 androidboot.usbcontroller=a600000.dwc3 rootwait ro init=/i
Cmdline: nit buildvariant=user androidboot.verifiedbootstate=green androidboot.keymaster=1 dm="1 vroot none ro 1,0 5764704 verity 1 PARTUUID=58c917ed-d09f-07d
Cmdline: 1-c7af-88f6e15beec8 PARTUUID=58c917ed-d09f-07d1-c7af-88f6e15beec8 4096 4096 720588 720588 sha1 97af17c8d79629d048c11ad4058f343124279389 61efba56aa1fa
Cmdline: 6397190a518f0aa794bbd02a7f604536e1a402ccb5f173614b7 10 restart_on_corruption ignore_zero_blocks use_fec_from_device PARTUUID=58c917ed-d09f-07d1-c7af-
Cmdline: 88f6e15beec8 fec_roots 2 fec_blocks 726263 fec_start 726263" root=/dev/dm-0 androidboot.vbmeta.device=PARTUUID=c3e4fce8-096e-a707-d9d8-74da4faf0882 a
Cmdline: ndroidboot.vbmeta.device=PARTUUID=c3e4fce8-096e-a707-d9d8-74da4faf0882 androidboot.vbmeta.avb_version=1.0 androidboot.vbmeta.device_state=locked andr
Cmdline: oidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=7616 androidboot.vbmeta.digest=5ae631ed1c9936e5ea64c854419ef7d150b317798975bc7d94fe444267daeac
Cmdline: 2 androidboot.vbmeta.invalidate_on_error=yes androidboot.veritymode=enforcing androidboot.bootdevice=1d84000.ufshc androidboot.fstab_suffix=default a
Cmdline: ndroidboot.serialno=221e6695 androidboot.baseband=msm msm_drm.dsi_display0=dsi_samsung_s6e3fc2x01_cmd_display: androidboot.slot_suffix=_b skip_initra
Cmdline: mfs rootwait ro init=/init androidboot.dtbo_idx=21 androidboot.dtb_idx=1 panel_type=black androidboot.mode=normal androidboot.recoveryreason=000 andr
Cmdline: oidboot.project_name=18811 androidboot.project_codename=fajitat ddr_manufacture_info=Samsung ddr_row0_info=16 androidboot.hw_version=41 androidboot.r
Cmdline: f_version=11 androidboot.prj_version=0 androidboot.platform_id=321 androidboot.platform_name=SDM845 androidboot.startupmode=hard_reset androidboot.en
Cmdline: able_dm_verity=1 androidboot.at_location=factory androidboot.power_cut_test=0 androidboot.secboot=enabled androidboot.battery.absent=false androidboo
Cmdline: t.rpmb_enable=true androidboot.type=sdebug androidboot.product.hardware.sku=3 androidboot.logcat_log_level=49 androidboot.init_log_level=49 androidbo
Cmdline: ot.selinux_log_level=842348088 androidboot.cust=0 androidboot.prmec=true androidboot.opcarrier=none androidboot.bootcount=1919247457

В глаза бросился нестандартный параметр androidboot.type=sdebug. Хотя сам по себе флаг androidboot.type не является чем-то уникальным, упоминаний его выставления в sdebug оказалось немного. В ходе поиска удалось найти скрипт init.oem.rc, включающий adb (в том числе в режиме зарядки) в случае, если устройство запущено в режиме sdebug:

on property:sys.boot_completed=1 && persist.vendor.usb.config=none && property:ro.boot.type=sdebug
    setprop persist.sys.usb.config adb
...
on charger && property:ro.boot.type=sdebug
    setprop sys.usb.config adb

Однако на исследуемом устройстве этот файл найти не удалось (позднее выяснилось, что он был встроен в бинарный файл init). Тогда было принято решение изучить все элементы цепочки загрузки устройства в надежде найти место включения режима отладки. К этому моменту стало очевидно, что данный функционал устройства каким-то образом связан с оставленным производителем бэкдором.
В 2017 году в устройствах OnePlus уже находили бэкдор. Однако с того момента прошло уже 4 года (на устройстве стоит прошивка 2021 года).

Анализ процесса загрузки показал, что в бинарном файле init присутствует логика обработки данного параметра.
В функции android::init::PropertyLoadBootDefaults происходит обработка параметра ro.boot.type. В случае, если он имеет значение sdebug, выставляются параметры ro.debuggable=1 и ro.adb.secure=0, что и можно наблюдать на устройстве. Кроме того, в приведенном ниже фрагменте кода видно, что параметр ro.boot.verifiedbootstate при этом выставляется в orange:

Фрагмент кода init загрузчика
    ...
    std::string value = android::base::GetProperty("ro.boot.type", empty_str);
    if(std::string::compare(value, 5, "debug")){
        android::base::SetProperty("ro.secure", "0");
        android::base::SetProperty("ro.adb.secure", "0");
        android::base::SetProperty("ro.boot.verifiedbootstate", "orange");
        android::base::SetProperty("ro.debuggable", "1");
        android::base::SetProperty("ro.force.debuggable", "1");
    }
    if(std::string::compare(value, 6, "sdebug")){
        android::base::SetProperty("ro.adb.secure", "0");
        android::base::SetProperty("ro.boot.verifiedbootstate", "orange");
        android::base::SetProperty("ro.debuggable", "1");
        android::base::SetProperty("ro.force.debuggable", "1");
    }
    ...

Но если проверить свойство ro.boot.verifiedbootstate, его значение будет green. Неужели этот код не выполняется, и выставление параметров осуществляется в каком-то другом месте?

OnePlus6T:/ # getprop ro.boot.verifiedbootstate
green

Как оказалось, все намного проще: свойства типа ro в init процессе с использованием функции android::base::SetProperty можно выставить только один раз. Первоначально init реализует обработку командной строки ядра в функции ProcessKernelCmdline, где и выставляет передаваемые с префиксом androidboot. аргументы ядра в качестве параметров с префиксом ro.boot.. В том числе выставляется параметр ro.boot.verifiedbootstate=green, поскольку в командной строке было передано значение androidboot.verifiedbootstate. И только после этого вызывается функция android::init::PropertyLoadBootDefaults.

Не углубляясь в исходный код init, достаточно заметить, что в функции android::init::PropertyLoadBootDefaults уже используется параметр ro.boot.type. Следовательно он должен быть выставлен ранее.

Погрузимся глубже.

2. Как в командную строку ядра Android попала строка androidboot.type=sdebug?

Если вы не знакомы с тем, как устроен процесс загрузки современного Android-устройства, рекомендуем познакомиться с этой статьей.
Опустим детали и сразу перейдем к фрагменту реализации загрузчика LinuxLoader, запускаемого на финальном шаге.

Данный загрузчик имеет GUID F536D559-459F-48FA-8BBC-43B554ECAE8D и, судя по всему, основан на предоставляемом qualcomm edk2 приложении.

При поиске по строкам значения sdebug можно достаточно быстро обнаружить следующий участок кода:

boot_tag = get_boot_tag();
switch (boot_tag){
    case 0xB8:
        boot_type = "nonrelease";
        break;
    case 0xA9E:
        boot_type = "sdebug";
        break;
    case 0xA0:
        boot_type = "auto";
        break;
    case 0xB7:
        boot_type = "debug";
        break;
    default:
        boot_type = "normal";
        break;
}
AsciiVSPrint(&buf, 64, " androidboot.type=%a", &boot_type);
set_sdebug_boot_type(&buf);

В зависимости от возвращаемого функцией get_boot_tag значения выставляется аргумент androidboot.type, который затем помещается в передаваемую ядру командную строку (детали этого процесса опустим).
Функция get_boot_tag в свою очередь реализует получение параметра из хранилища param (размещается в одноименном разделе прошивки). Для изменения этого значения в коде загрузчика реализована функция set_boot_tag:

int get_boot_tag(){
    int tmp;
    get_param_by_index_and_offset(0x12Cu, 0x84u, &tmp, 4u);
    return tmp;
}

void set_boot_tag(int* tag){
    set_param_by_index_and_offset(0x12Cu, 0x84u, tag, 4u);
}

Парсер param секции устройств OnePlus реализован тут, более подробно с форматом можно ознакомиться там же.

Таким образом, приходим к выводу, что появление параметра androidboot.type=sdebug в командной строке ядра напрямую связано с неким параметром, хранимым в секции param устройства. Попробуем продолжить цепочку и разобраться, как же он туда попал.

Если взглянуть на вызовы функции set_boot_tag, можно наткнуться на единственную ссылку из обработчика "секретной" команды ops boottype :

int ops_boottype_handler(wchar_t *arg){
    int tmp
    get_param_intranet(&tmp);
    if ( (get_boot_tag() != 0xA9E || !tmp)
        && !enable_advanced_ops )
    {
        fastboot_fail("unknown command");
        return;
    }
    DebugPrint(0x80000000, "fastboot cmdlist: cmdname=%s\n", arg);
    tmp = strcmp(arg, L" normal");
    if ( tmp )
    {
        if ( !strcmp(arg, L" auto") )
        {
            tmp = 0xA0;
        }
        else if ( !strcmp(arg, L" debug") )
        {
            tmp = 0xB7;
        }
        else if ( !strcmp(arg, L" sdebug") )
        {
            tmp = 0xA9E;
        }
        else if ( !strcmp(arg, L" nonrelease") )
        {
            tmp = 0xB8;
        }
        else{
            fastboot_fail((__int64)"unknown command");
            return;
        }
    }
    set_boot_tag(tmp);
    fastboot_okay("");
    return;
}

Команда "секретная", поскольку ее обработчик объявлен в общей таблице со всеми командами типа ops, наименование которых хранится в закодированном виде. Полный список команд приведен под спойлером. Помимо безобидных команд также поддерживается изменение режима работы selinux (ops selinux), отключение и включение dm verity (ops disable_dm_verity и ops enable_dm_verity). И все это – на заблокированном загрузчике без каких-либо следов вмешательства после восстановления оригинального значения параметров.

Таблица поддерживаемых команд ops
encoded_cmds_60720[] = {
    "jqtxsth%xut", ops_console,
    "jqtxsthsz%xut", ops_unconsole,
    "yhjyji%pfjqrjrp%xut", ops_kmemleak_detect,
    "yhjyjisz%pfjqrjrp%xut", ops_kmemleak_undetect,
    "s|tiyzmx2yttgjw%xut", ops_reboot_shutdown,
    "wjiftqyttg2yttgjw%xut", ops_reboot_bootloader,
    "lsnsnfwydjhwtk%xut", ops_force_training,
    "lsnsnfwydjhwtksz%xut", ops_unforce_training,
    "xkfyfi%xut", ops_datafs,
    "uqjm%xut", ops_help,
    "urzi%xut", ops_dump,
    "}zsnqjx%xut", ops_selinux,
    "jitrdyttg%xut", ops_boot_mode,
    "65K:IYXK<WYK=6WY5955:K9%xut", ops_4F50040TR18FTR7FSTD5F01,
    "75K:IYXK<WYK=6WY5955:K9%xut", ops_4F50040TR18FTR7FSTD5F02,
    "~ynwj{dridjqgfxni%xut", ops_disable_dm_verity,
    "~ynwj{dridjqgfsj%xut", ops_enable_dm_verity,
    "xkurjy%ysztr%xut", ops_mount_tempfs,
    "xkurjy%ysztrsz%xut", ops_unmount_tempfs,
    "lrndfyfidlsnlfjv%xut", ops_qeaging_data_img,
    "gifdjhwtk%xut", ops_force_adb,
    "yxzhdyjx%xut", ops_set_cust,
    "ju~yyttg%xut", ops_boottype,
    "lxrp{ji%xut", ops_devkmsg,
    "qj{jqdltqdqjswjp%xut", ops_kernel_log_level,
    "qj{jqdltqdyfhltq%xut", ops_logcat_log_level,
    "qj{jqdltqdynsn%xut", ops_init_log_level,
    "qj{jqdltqd}zsnqjx%xut", ops_selinux_log_level,
    "kwd|mdowu%xut", ops_prj_hw_rf,
    "tksn2jhn{ji%xut",ops_device_info,
    "stnyfhtqdyf%xut",ops_at_location,
    "yxjydyzhdwj|tu%xut",ops_power_cut_test,
    "yjxjwdrwf|dhnru%xut",ops_pmic_warm_reset,
    "owudxzqut%xut",ops_oplus_prj,
    "nxyidxzqut%xut",ops_oplus_dtsi,
    "lfqkiftqidwj{thjw%xut",ops_recover_dloadflag,
}

Все эти функции добавляются в связный список обработчиков команд `fastboot` в декодированном виде функцией `fastboot_add_encoded_cmd` (наименование автора). Декодирование наименований команд производится при помощи простого алгоритма:

def decode(cmd):
    res = ""
    for i in cmd[::-1]:
        res += chr(ord(i)-5)
    return res

В основном цикле обработки fastboot-команд можно видеть некоторые условия их выполнения. Для большей загадочности в функции `is_ops_commands` проверяемая команда предварительно кодируется и в таком виде сравнивается со значением `xut`:

if ( !is_ops_commands(cmd->prefix) || get_ops_allowed() || get_param_intranet_status())
{
    cmd->handle(&input_cmd[cmd->prefix_len], download_base, download_size);
}
else
{
    fastboot_fail("unknown command");
}

Остается разобраться с функцией get_ops_allowed. Она возвращает некоторое значение op_token_count, которое обнуляется в нескольких случаях: загадочной командой oem unoproot, по истечении 10 загрузок устройства (не во всех случаях) или выполнения команды ops 4F50040TR18FTR7FSTD5F02. Ненулевым значением данная переменная инициализируется только в случае выполнения команды flash:oproot с некоторым подписанным на ключе платформы токеном. Куда интереснее непонятный параметр intranet, выставление которого в значение 3 также приводит к разблокировке описанных выше команд.

3. Как выключить этот режим?

Целью было выключение неведомого режима, а поскольку стандартная реализация fastboot не позволяет отправлять команды типа ops для взаимодействия с устройством, был использован пакет adb для python:

from adb import fastboot
fastboot_dev = fastboot.FastbootCommands()
fastboot_dev.ConnectDevice()
fastboot_dev._SimpleCommand(b"ops 4F50040TR18FTR7FSTD5F01")
fastboot_dev._SimpleCommand(b"ops boottype normal")
fastboot_dev._SimpleCommand(b"ops 4F50040TR18FTR7FSTD5F02")

В результате выполнения скрипта root-доступ на устройстве исчез, а выполнение команд ops стало невозможным.

4. А как вернуть это режим?

После выполнения пункта 3 возник вопрос: а возможно ли вернуть этот режим?
Уже после блокировки данного режима выяснилось, что для его разблокировки необходимо иметь специальный подписанный на ключе производителя токен. Получить помещаемые в токен данные можно командой oem oproot, а разблокируется данный режим путем прошивки специального файла oproot (flash:oproot).

Дабы продолжить эксперименты с рутованным девайсом, было принято решение воспользоваться инструментом edl для прошивки на устройство модифицированного раздела param. Путем выполнения нехитрых команд root-доступ был успешно восстановлен:

edl printgpt
edl r param param.bin
py op8t_param.py read -f param.bin
py op8t_param.py write -f param.bin -o param_patched.bin 16 2718
edl w param param_patched.bin

Также удивило и то, что в репозитории edl нашелся скрипт oneplus_param.py, судя по всему основанный на результатах исследований APK-файла из выявленного в 2017 году бэкдора.
В скрипте подробно описаны варианты значений различных флагов этого раздела и перечислены команды ops. Также в нем представлен код для генерации QR для разблокировки с использованием инженерного приложения. На прошивке 2021 года данная процедура не актуальна.

Послесловие

Точно выяснить историю включения этого режима на устройстве, увы, уже не представляется возможным. Предположительно его появление связано с перепрошивкой на глобальную версию с помощью MSM Download Tool, проведенной перед продажей устройства. Впоследствии смартфон получал обновления, но активированный режим продолжал функционировать и был замечен благодаря усилению antiroot-механизмов в банковских приложениях.
Надеемся, что данная статья позволила вам глубже погрузиться в особенности работы смартфонов OnePlus и осознать, что даже самые современные устройства могут таить в себе неожиданные сюрпризы. Дальнейшие исследования этого функционала вряд ли имеют смысл, так как сейчас он представляет больше исторический интерес и отсутствует на новых моделях (по крайней мере, проверялся OnePlus 12).
Если у вас есть схожий опыт или интересные мысли по теме, делитесь ими в комментариях!

Теги:
Хабы:
+39
Комментарии2

Публикации

Информация

Сайт
ntc-vulkan.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия

Истории