Хабрахабр

Поиск уязвимостей в Samsung TrustZone, или AFL зафаззит все

Хотя это не совсем так. С течением времени появляется все больше новых защитных технологий, из-за которых хакерам приходится все туже затягивать пояса. Защитные технологии также всегда вносят дополнительную поверхность атаки, и чтобы их обходить, надо только задействовать уязвимости в их коде.

Как оказывается, ее реализации содержат огромный объем кода, и чтобы искать в них уязвимости, нужен какой-то автоматический способ. Посмотрим на одну из таких технологий — ARM TrustZone. Но умный! Задействуем старый проверенный метод — фаззинг.

Чтобы хорошенько рассказать про выбранный нами способ фаззинга, необходимо привести сначала немного теории про TrustZone, доверенные операционные системы и взаимодействие с обычной операционной системой. Будем фаззить специальные приложения, которые появились с введением технологии TrustZone — трастлеты. Поехали! Это недолго.

ARM TrustZone

Такой обработкой занимаются, например, сервисы Keystore, Fingerprint в ОС Android, технологии защиты авторских прав DRM и др. Технология TrustZone в процессорах ARM позволяет перенести обработку конфиденциальной информации в изолированную защищенную среду.

Про устройство TrustZone уже написано очень много, поэтому мы только кратко напомним.

TrustZone разделяет "мир" (в терминах TrustZone — World) на два — Normal World и Secure World — и добавляет в процессор целых четыре режима исполнения:

  • EL3 — режим монитора — режим, в котором система стартует, и который является самым привилегированным режимом исполнения;
  • S-EL2 — режим доверенного гипервизора;
  • S-EL1 — режим доверенной операционной системы;
  • S-EL0 — режим доверенных приложений (trusted applications, TAs, trustlets), или трастлетов.

Одна, которая работает в Normal World, называется Rich OS, а вторая — из Secure World — TEE (Trusted Execution Environment) OS. На SoC с технологией TrustZone, могут одновременно работать сразу две операционные системы. Мы сосредоточимся на одной конкретной — Trustonic Kinibi. Существует уже не один десяток этих доверенных операционных систем. Она, в частности, используется на телефонах Samsung с SoC Exynos включительно до Galaxy S9.

Trustonic Kinibi

Компания Trustonic была создана компаниями ARM, Gemalto и Giesecke & Devrient (G&D) и продолжила развитие операционной системы Mobicore компании Giesecke & Devrient (G&D) под именем Kinibi.

Ее структурная схема приведена на рисунке. Операционная система Kinibi поддерживает стандарты Global Platform Trusted Execution Environment.

Чтобы разобраться с основными из них, лучше посмотреть на схему этой системы с точки зрения разработчика. Как видно, имплементация TrustZone включает компоненты не только в "защищенном мире", но и компоненты в "нормальном мире".

А в нормальном мире работает специальный драйвер, обеспечивающий переход процессора в защищенный мир по запросу приложений. На низком уровне в защищенном мире, кроме микроядра, работают драйверы и менеджер рантайма. Также в нормальном мире работает специальный демон, который обеспечивает начальный запуск некоторых трастлетов, и через который проходят все запросы к трастлетам из клиентских приложений. На уровне userspace работают приложения и компоненты обеспечивающие API для связи приложений нормального и защищенного миров.

Оба набора обеспечивают примерно одинаковый набор функций, только первый построен по стандартам Global Platform, а второй, похоже, был еще до стандарта, и поэтому именуется Legacy. Наборов API в Kinibi два: Global Platform API (обозначен зеленым цветом) и Legacy API (красный). Несмотря на то, что, судя по названию, от его использования надо уходить, в трастлетах Samsung используется только Legacy API.

Взаимодействие между мирами

Трастлеты реализуют различные функции: аутентификация, управление ключами, работа с аппаратными компонентами, реализующими функции безопасности, и др. Чтобы использовать возможности, предоставляемые технологией TrustZone, приложения в нормальном мире, называемые клиентскими приложениями, общаются с доверенными приложениями — трастлетами.

Нормальный мир и защищенный мир, по технологии TrustZone, на верхних уровнях (EL0 и S-EL0) изолированы друг от друга по памяти, и чтобы создать такой регион общей между ними памяти, называемой World Shared Memory (WSM), используется API, предоставляемый защищенным миром. Запросы к трастлетам передаются с помощью специальной общей памяти.

Общая схема взаимодействия клиенского приложения и трастлета выглядит примерно так:

1) клиентское приложение обращается к демону с UID трастлета, с которым хочет установить сессию;
2) демон посредством драйвера обращается к доверенной операционной системе с запросом на загрузку трастлета;
3) доверенная операционная система загружает трастлет в адресное пространство защищенного мира;
4) клиентское приложение опять через запрос к демону создает буфер WSM и записывает в него данные для запроса к трастлету;
5) клиентское приложение уведомляет защищенный мир о готовности запроса;
6) в защищенном мире запрос отправляется на обработку нужному трастлету, и трастлет записывает в буфер WSM результат своей работы;
7) цикл запроса и ответа может повториться;
8) клиентское приложение завершает сессию с трастлетом.

Для клиентского приложения: Псевдокоды сессии взаимодействия для клиентского приложения и для трастлета выглядят довольно шаблонно.

void main()
{ uint8_t* tciBuffer; uint32_t tciLength; uint8_t* mem; uint32_t mem_size; mcOpenDevice(MC_DEVICE_ID_DEFAULT); mcMallocWsm(MC_DEVICE_ID_DEFAULT, 0, tciLength, &tciBuffer, 0); session.deviceId = MC_DEVICE_ID_DEFAULT; mcOpenSession(&session, &uuid, tciBuffer, tciLength); mcMap(&session, mem, mem_size, &mapInfo); mcNotify(&session); mcWaitNotification(&session, -1); mcUnmap(&session, mem1, &mapInfo1); mcCloseSession(&session); mcFreeWsm(MC_DEVICE_ID_DEFAULT, tciBuffer); mcCloseDevice(MC_DEVICE_ID_DEFAULT);
}

Для трастлета:

void tlMain(uint8_t *tciData, uint32_t tciLen)
// Trusted Application main loop for (;;) { // Wait for a notification to arrive tlApiWaitNotification(INFINITE_TIMEOUT); // Process command // Notify the TLC tlApiNotify(); }
}

Также у клиентского приложения есть возможность использовать функцию mcMap. mcNotify/tlApiNotify и mcWaitNotification/tlApiWaitNotification — это и есть те самые функции уведомления о том, что запрос/ответ готов для получения в другом мире, и функции ожидания обработки запроса. Всего с помощью этой функции можно создать только четыре таких буфера. Она позволяет создать еще один буфер WSM, если это требуется.

А что же представляют собой трастлеты? С клиентскими приложениями понятно — для телефонов Samsung это обычные приложения в Android.

Трастлеты Kinibi

Это не привычный для Android формат ELF или APK. Трастлеты находятся в обычной файловой системе устройства и являются файлами, содержащими исполняемый код. Он описан в открытых исходных кодах компонентов уровня userspace, которые Trustonic разместил на Github. Трастлеты в операционной системе Kinibi имеют собственный формат MobiCore Load Format (MCLF). Схематично структуру файла трастлета можно изобразить на такой картинке (трастлет — слева).

Для трастлетов можно выделить следующие особенности:

  • исполняются в изолированном адресном пространстве, то есть один трастлет не видит другой;
  • не имеют доступа к памяти нормального мира, за исключением буферов WSM, к памяти операционной системы TEE и к физической памяти;
  • располагаются в памяти по секциям с разными правами на чтение, запись и исполнение;
  • буферы WSM располагаются в неисполняемой памяти;
  • загружаются без ASLR;
  • в своей работе используют API, предоставляемый mclib — библиотекой, реализующей Global Platform API и Legacy API для защищенного мира;
  • могут обращаться к защищенным драйверам с помощью функции tlApi_callDriver.

Кроме этого, они используют некоторые защитные механизмы, типа различных аттрибутов памяти, и также большинство трастлетов используют stack canaries для защиты от эксплуатации перезаписи стека. Как видим, трастлеты довольно ограничены в возможностях. А вот ASLR в Kinibi нет, хотя он планируется в новых версиях.

Несмотря на все ограничения, трастлеты — очень интересная цель для атакующего по следующим причинам:

  • это окно в TrustZone с уровня userspace в Android;
  • могут служить отправной точкой для эскалации привилегий до ядра операционной системы TEE;
  • трастлеты имеют доступ к защищенной информации, куда не имеет доступ даже ядро Android.

Если поискать в нем трастлеты, то окажется, что их там довольно много. В качестве подопытной устройства мы использовали Samsung Galaxy S8.

Использовать для поиска узявимостей статический анализ бинарного кода кажется не очень надежной идеей. То есть и кода очень много. Хорошо бы еще использовать зарекомендовавший себя метод фаззинга с обратной связью, и каким-либо образом ловить краши трастлетов, когда они будут случаться. Задействовать динамический анализ так просто не получится, хотя бы потому что трастлеты имеют свой формат, отличный от того, что может запускаться на традиционных операционных системах. Попробуем решить эту интересная задачку.

Как же это фаззить?

А все остальные, вероятно, знают, что AFL умеет фаззить файлы формата ELF. Для тех, кто еще не познакомился с замечательным инструментом AFL и с его многочисленными надстройками, рекомендуем к прочтению эту хорошую статью. Это достигается за счет режима qemu mode. Причем даже бинарные файлы, скомпилированные изначально без инструментации AFL. Это позволяет ему проводить фаззинг с контролем покрытия кода даже для бинарных файлов. AFL использует специальную сборку эмулятора qemu, в которой к режиму qemu user mode добавлена функциональность бинарной инструментации инструкций ветвления. Но для того чтобы использовать этот режим в нашей задаче, надо как-то сконвертировать трастлеты в формат ELF. А бонусом к этому идет возможность фаззить исполняемые файлы не только родной архитектуры, но и всех архитектур, которые поддерживает qemu.

Благодаря открытому формату, для IDA Pro существует загрузчик для них. Посмотрим повнимательнее на файлы трастлетов. Интересно то, что все вызовы таких функций проходят через одну функцию по адресу, записанному в заголовке трастлета. Если открыть любой трастлет, кроме, собственно, его кода, можно увидеть, что он использует функции библиотеки mclib. Например, так выглядит функция tlApiLogvPrintf в коде трастлета, которая очевидно занимается выводом строк.

Это функция диспетчеризации mclib, адрес которой записан в заголовке MCLF в поле, которое называется tlApiLibEntry. Видно, что она пробрасывает все параметры дальше другой функции. Значит если мы реализуем заглушки для функций API, мы сможем исполнить код трастлета в обычной среде Linux, конечно, сначала как-то преобразовав его в файл ELF. То есть библиотечные функции, вызываемые таким образом, это единственная зависимость для трастлетов, других ссылок вовне трастлеты не имеют. А значит и сможем отлаживать и фаззить его.

Easy! Чтобы превратить трастлет в файл формата ELF, можно взять уже готовый файл, например, скомпилировать пустое приложение с функцией main, и добавить в него секции трастлета вместе с его заголовком. С этим тоже нет проблем, в заголовке трастлета есть адрес его точки входа. Также необходимо как-то передать управление на код трастлета. Подумав и поэкспериментировав, можно набросать следующий план решения нашей задачи: Определим этот адрес в нашей функции main как адрес функции и вызовем ее.

1) реализовать передачу исполнения на точку входа трастлета;
2) реализовать библиотечные функции или заглушки к ним;
3) реализовать функцию диспетчеризации и записать ее адрес в заголовок трастлета;
4) расположить секции трастлета по нужным адресам.

Для каждого трастлета надо автоматически определять следующие параметры: точку входа, адреса секций трастлета и размер входного буфера WSM. Так как нам надо превратить в эльфы сразу много трастлетов, надо подумать над автоматизацией этих задач. Добавим это в план.

5) определить точку входа, адреса секций и размер буфера WSM.

Собираем эльфа

1) Точка входа

Его можно добавить в функцию main для нашего исходного файла ELF. Первый пункт плана легко реализовать следующим кодом.

typedef void (*tlMain_t)(const void* tciBuffer, const uint32_t tciBufferLen);
tlMain_t tlMain = sym_tlMain; tlMain(tciBuffer, tciBufferLen);

Компилируем наш код в объектный файл.

$(CC) $(INCLUDE) -g -c tlrun.c

Это можно сделать с помощью objcopy. Символ sym_tlMain надо добавить в объектный файл.

arm-linux-gnueabi-objcopy --add-symbol sym_tlMain=$(TLMAIN) tlrun.o tlrun.o.1

1 — скомпилированный исходник с функцией main, передающей управление на код трастлета. В результате получаем tlrun.o.

2) Библиотечные функции

Когда-то давно была утечка из Qualcomm с кучей материалов для мобильных устройств на базе их процессоров. Чтобы реализовать библиотечные функции, для начала надо иметь список всех этих функций. Оттуда мы взяли прототипы библиотечных функций с их номерами, передаваемые как параметр в функцию диспетчеризации. Среди этих материалов также были некоторые образы, заголовочные файлы и отладочные образы некоторых компонентов для операционной системы mobicore. А функции не настолько понятные, например tlApiSecSPICmd, мы заменили простыми заглушками, выводящими свое имя и возвращающими статус ОК. Для функций с известным предназначением, типа tlApiMalloc или tlApiLogvPrintf, мы сделали соответствующие реализации с помощью аналогичных функций из libc. Весь API компилируется в файл tllib.o

$(CC) $(INCLUDE) -g -c tllib.c

3) Функция диспетчеризации

Аналогично адресу точки входа добавляем символ, его адрес одинаковый для всех трастлетов:

arm-linux-gnueabi-objcopy --add-symbol sym_tlApiLibEntry=0x108c tlrun.o tlrun.o.1

Надо только учесть, что ее адрес необходимо записать в заголовок. Реализация функции диспетчеризации тривиальна. Например, при запуске файла до начала исполнения функции main. Так как мы не знаем заранее по какому адресу после линковки и запуска расположится наша функция диспетчеризации, записывать ее адрес в заголовок трастлета надо уже в рантайме.

void (*sym_tlApiLibEntry)(int num) __attribute__((weak));
void tlApiLibEntry(int num) __attribute__((noplt)); __attribute__((constructor)) void init()
{ sym_tlApiLibEntry = tlApiLibEntry;
}

4) Секции

Добавить секции к объектному файлу также используем objcopy.

arm-linux-gnueabi-objcopy --add-section .tlbin_text=.text.bin \ --set-section-flags .tlbin_text=code,contents,alloc,load \ --add-section .tlbin_data=.data.bin \ --set-section-flags .tlbin_data=contents,alloc,load \ --add-section .tlbin_bss=.bss.bin \ --set-section-flags .tlbin_bss=contents,alloc,load \ tlrun.o.1 tlrun.o.2

Дамп в файл можно сделать с помощью все той же IDA. Здесь .tlbin_text — название секции трастлета, а .text.bin — название файла с дампом этой секции.

В результате этого преобразования, к исходному файлу ELF добавится трастлет в бинарном виде.

5) Автоматизация

Для каждого трастлета надо определить точку входа, адреса секций и размер буфера WSM. Для всей сборки мы решили использовать один большой Makefile общий для всех трастлетов и еще по одному маленькому, подключаемому к нему для каждого отдельного трастлета с его параметрами. Можно автоматизировать и эту задачу, а можно потратить 10 минут, чтобы определить его для всех трастлетов, анализируя их код вручную. Первые два параметра легко получить с помощью простого скрипта для IDA, а определение размера буфера иногда не так просто автоматизировать. Эти параметры можно задавать как переменные в своем маленьком Makefile.

TLMAIN := 0x98F5D
TLTEXT := 1000
TLDATA := c0000
TLBSS := c10e0
TLTCI_LEN := 4096

А в большом Makefile использовать эти параметры таким образом:

$(CC) $(INCLUDE) -g -DTCILEN=$(TLTCI_LEN) -c tlrun.c
# ...
$(CC) -g tlrun.o.2 tllib.o --section-start=.tlbin_text=$(TLTEXT),--section-start=.tlbin_data=$(TLDATA),--section-start=.tlbin_bss=$(TLBSS) -o tlrun

В теории он даже может корректно исполняться и дальше фаззиться. Итак, мы превратили трастлет в файл формата ELF с правильным расположением секций трастлета в памяти и правильными адресами в заголовке. Ну так давайте это проверим!

Фаззинг

И тут сразу же начались проблемы. Так как AFL использует qemu для исполнения кода не родной архитектуры, для начала неплохо бы проверить, исполняется ли вообще наш эльф под эмулятором.

Проблема №1: тулчейн

"hf" в конце означает использование компилятором аппаратной поддержки Hard float в процессорах ARM. Для компиляции кода и сборки файла мы использовали тулчейн arm-linux-gnueabihf. При том что у нас в коде нигде не было работы с числами с плавающей точкой, причина этого падения была абсолютно не понятна. При попытке запустить наш файл под эмулятором qemu сразу падал, выдавая "Segmentation fault". И нам повезло! Немного подумав, мы решили попробовать использовать тулчейн без Hard float arm-linux-gnueabi. Файл заработал и в консоли стал появляться вывод от него.

Запускаем AFL и тут... Значит можно фаззить.

Проблема №2: инструментация

Сначала было абсолютно не понятно, в чем же проблема. Почему-то AFL не видит инструментацию. Чертыхаясь, пришлось влезть в исходники патчей AFL для qemu. qemu собрана правильно, опция -Q (qemu mode) установлена. Проблема в том, что если кодовых секций несколько, инструментироваться будет почему-то только первая из них. Оказывается, в патчах AFL при загрузке файла ELF qemu ищет кодовую секцию и устанавливает границы адресов, в которых собирается производить инструментацию. Очевидно, инструментацию он не видит при запуске, потому что во второй секции ее нет! Баг это или фича, но у нас кодовых секций две, и точка входа — main — находится во второй. Включаем ее и запускаем. Шерстя дальше исходники, можно заметить, что при включенной переменной среды AFL_INST_LIBS границы инструментации становятся бесконечными.

Фаззинг работает!

Мы запустили фаззинг с обратной связью на бинарных файлах кастомного формата. Идея подтвердилась! Таким образом, мы получили надежный способ фаззить такие бинари, отлавливать ошибки в их коде и к тому же запускать их в обычном Линуксе и удобно отлаживать существующими инструметами. Как видим, он даже находит какие-то краши. Класс!

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

Анализируем краши

Огромный объем, который совершенно не хочется обрабатывать вручную. Суммарно для 23 трастлетов AFL нашел 477 тесткейсов, генерирующих краши. Чтобы убрать избыточность тесткейсов, можно воспользоваться инструментом afl-cmin. Среди этого множества тесткейсов есть почти одинаковые, генерирующие краш в одном и том же месте. Все равно очень много! После прохода им по всем трастлетам осталось 225 случаев, которые предстоит проанализировать. Это поможет оценить эксплуатабельность баги и трудоемкость ее эксплуатации. Чтобы как-то облегчить себе задачу, мы решили использовать инструменты динамического анализа, которые помогут более точно идентифицировать программную ошибку и какие-либо ее свойства.

Для этого могут подойти Linux или Android. Итак, чтобы использовать какие-то инструменты динамического анализа, нам надо по крайней мере запустить наши переделанные трастлеты на нативной системе ARM, а не под виртуализацией qemu.

Проблема №3: секции

трастлеты 32-битные, а Linux более удобен и содержит больше инструментов для динамического анализа, чем Android. Мы решили взять 32-битную систему с Linux, т.к. И вот тут оказалось, что при запуске наши эльфы сразу выдают Segmentation fault.

При их создании надо распологать секции трастлета по нужным адресам, где адрес кодовой секции трастлета всегда 0x1000. Выяснилось, что проблема в необычности наших бинарей. А в системе Linux первые две страницы адресного пространства, до адреса 0x2000, зарезервированы для служебных задач, поэтому, когда загрузчик пытается спроецировать туда секцию, и возникает ошибка. Это первая секция в файле, а перед ней еще располагается заголовок ELF по адресу 0x0.

На 64-битном ядре такого резервирования первых страниц в памяти не происходит, и становится возможным такое расположение секций. Как оказалось, выход из этой ситуации есть. Для этих целей отлично подходит пакет debootstrap. Так как наши файлы 32-битные, то удобно предварительно создать на 64-битной системе 32-битную среду.

Проблема №4: нет инструментов

Среди методов динамического анализа бинарных файлов есть отладка и динамическая бинарная инструментация (DBI). Теперь, когда наши переделанные трастлеты работают на нативной системе ARM, надо попробовать на них инструменты динамического анализа. А для второго не так много опций: под ARM есть по сути только три стабильных фреймворка DBI — DynamoRIO, Valgrind и Frida. Для первого отлично подходит gdb. Valgrind довольно мощный фреймворк, и у него есть подходящие нам инструменты callgrind для трассировки и memcheck для мониторинга операций с памятью. Первый имеет много хороших инструментов для трассировки и отлова ошибок, но загрузчик файлов ELF, который в нем реализован, не справился с загрузкой наших файлов. А Frida мы не успели попробовать. Оказалось, что они выдают не очень удобные для парсинга результаты, поэтому не подходят для использования в автоматическом режиме на множестве файлов. Если кто-нибудь имел опыт использования в Linux на ARM, напишите ваши впечатления в комментариях.

Но с использованием скриптов для gdb даже это уже значительно упрощает нам работу. Как видим, нам остается довольствоваться только отладчиком.

Проблема №5: библиотечные функции

Мы их заменили заглушками, за исключением функций, которые можно заменить аналогичными из libc. Еще одна проблема, которая была понятна с самого начала, это библиотечные функции, которые использует трастлет. Очевидно, что если в логике трастлета некоторый код обрабатывает результат работы одной из таких функций-заглушек, очень вероятно, что он упадет с ошибкой из-за того, что ожидает совершенно другие данные, и это не обязательно будет означать ошибку в коде.

Таких функций, для которых нельзя так просто сымитировать поведение реальной функции, довольно много:

  • tlApiSecSPICmd;
  • tlApi_callDriver;
  • tlApiWrapObjectExt;
  • tlApiUnWrapObjectExt;
  • tlApiCipherDoFinal;
  • tlApiSignatureSign;
  • ...

Чтобы не тратить время на изучение таких сомнительных случаев, мы решили просто не рассматривать тесткейсы, в которых используются эти функции.

Результаты фаззинга

В автоматическом режиме с помощью скриптов, мы собрали следующую информацию по всем трастлетам:

  • UID трастлета;
  • идентификатор краша;
  • тип ошибки (тип сигнала при краше);
  • адрес, на котором происходит ошибка;
  • используемые трастлетом функции API.

Всю эту информацию, как оказалось, очень удобно поместить в базу данных, и дальше запросами SQL выбирать наиболее интересные для анализа случаи и добавлять информацию по результатам анализа.

Например, таким запросом можно показать все тесткейсы, на которых происходит ошибка Segmentation fault:

select * from main where type = "SIGSEGV";

А таким отфильтровать тесткейсы, в которых используется функция tlApiSecSPICmd, которая у нас реализована заглушкой:

select * from main where api not like "tlApiSecSPICmd";

Некоторые из них не приводили к уязвимостям, но были и те, которые являются уязвимостями и могут быть использованы атакующим. Таким образом, во всех трастлетах были найдены ошибки разных типов. Рассмотрим самую интересную из найденных уязвимостей.

SVE-2019-14126

1, закодированной по правилам DER. Уязвимость была найдена в трастлете keymaster в коде обработки содержимого буфера TCI при парсинге структуры ASN. Очевидно, что если второй размер больше первого, происходит переполнение кучи. Два поля в этой структуре используются как размеры: один при выделении динамической памяти, а другой — при ее копировании. При оценке возможности эксплуатации надо также учитывать все ограничения трастлетов, перечисленные выше. Такие уязвимости обычно приводят к возможности выполнения кода атакующим, поэтому мы попытались сделать полноценный эксплойт для этой уязвимости.

Имея на руках переполнение кучи и исходя из данных ограничений, можно представить следующую стратегию эксплуатации:

  1. найти какой-нибудь указатель на функцию в доступном для перезаписи месте, например, в секции .bss;
  2. с помощью найденного переполнения создать в этом месте блок памяти кучи;
  3. инициировать выделение памяти в данном месте и перезапись указателя на функцию;
  4. инициировать вызов функции по перезаписанному указателю.

Для этого нам пришлось провести реверс-инжиниринг функций аллокации и освобождения памяти mclib, но теперь хорошее описание работы кучи можно посмотреть в этом докладе с конференции ZeroCon, прошедшей в апреле. Чтобы это сделать, конечно, надо понимать в деталях, как работает куча в операционной системе Kinibi.

Для этого нужно сначала с помощью переполнения создать в оригинальной куче блок, указывающий в .bss как на следующий доступный для аллокации блок. Еще одним немаловажный вопрос — как создать блок памяти кучи в секции .bss. Но для того, чтобы следующая аллокация произошла по этому адресу и не спровоцировала падение приложения надо, чтобы данные, которые уже там есть, могли быть истолкованы как корректные метаданные кучи.

Таким образом, мы нашли необходимый адрес в секции .bss, который можно использовать для релокации кучи.

Причем, это не может быть адрес нашего шеллкода, так как вся память доступная нам для записи, в том числе куча и секция .bss, не может исполняться. Перезаписав так указатель функции и инициировав ее вызов, можно передать исполнение на некоторый адрес. Поэтому единственной возможностью исполнения своего кода является использование техник code-reuse.

Действительно, мы можем использовать ROP, так как стэк в трастлетах располагается в самых последних адресах секции .bss. При этом слове на ум сразу приходит ROP. Это очень ненадежно, так как мы портим данные, которые могут еще использоваться в промежуток между аллокацией памяти и вызовом перезаписанной функции. Чтобы перезаписать адрес возврата в стэке придется перезаписать всю секцию неинициализированных данных, начиная с найденного адреса и до нужного места в стэке. Мы все же это попробовали, и, конечно, ничего не сработало.

JOP — это Jump Oriented Programming. Кроме ROP, можно еще использовать JOP. В JOP адрес для передачи исполнения извлекается из адреса в памяти или из регистра.

Он покажет все найденные гаджеты, а чтобы оставить только JOP, можно использовать регулярное выражение типа такого: Чтобы найти подходящие для JOP гаджеты подойдет классный инструмент ROPGadget.

ROPgadget --binary tlrun --thumb --range 0x1000-0xbeb44 | grep -E "; b.+ r[0-9]+$"

В нашем трастлете нашлось довольно много гаджетов. Нам повезло!

В ROP программировании для продвижения по цепочке используется встроенная в процессор логика возврата из функций. Теперь нам надо собрать их в цепочку. А в JOP для этого испльзуется некий супергаджет, который загрузит параметры для вызова из памяти и произведет вызов. Можно сказать, что ROP-программа пишется для weird machine, заключенной в архитектуре процессора. В архитектуре ARM, на которой и работает наш трастлет, есть инструкция подходящая на такую роль — LDMIA (Load Memory Increment Address).

Затем регистр с адресом увеличивается на размер прочитанных данных, так что при втором вызове этой инструкции будут читаться уже следующие данные. В качестве параметров она принимает регистр, содержащий адрес, данные по которому будут использоваться для загрузки в регистры, и набор регистров, которые необходимо заполнить. Это как раз то что надо для JOP!

Почему-то capstone, который используется в ROPGadget, интерпретирует эту инструкцию как LDMLO. Теперь надо найти гаджет с инструкцией LDMIA в нашем наборе.

Такие гаджеты есть. И нам опять повезло! Чтобы украсть stack cookie из стека, можно составить такую цепочку. Выберем из них один и используем его как супергаджет, а для построения цепочки будем использовать гаджеты из набора, полученного ранее.

*(int*)&mem1[offset] = SUPER_GADGET; // r2
*(int*)&mem1[offset + 4] = 0; // r3
*(int*)&mem1[offset + 8] = 0; // r4
*(int*)&mem1[offset + 12] = SUPER_GADGET; // r5
*(int*)&mem1[offset + 16] = 0x9560b; // r7 offset += 0x14;
*(int*)&mem1[offset] = 0; // r2
*(int*)&mem1[offset + 4] = 0; // r3
*(int*)&mem1[offset + 8] = 0; // r4
*(int*)&mem1[offset + 12] = 0; // r5
*(int*)&mem1[offset + 16] = 0x96829; // r7 offset += 0x14;
*(int*)&mem1[offset] = SUPER_GADGET; // r2
*(int*)&mem1[offset + 4] = 0; // r3
*(int*)&mem1[offset + 8] = 0x3d5f4; // r4
*(int*)&mem1[offset + 12] = mapInfo3.sVirtualAddr; // r5
*(int*)&mem1[offset + 16] = 0x218c7; // r7

Hello, world из защищенного мира будет выглядеть вот так.

strcpy(mem3 + 0x100, "Hello world from TEE!\n"); *(int*)&mem1[offset] = 0x7d081b1; // r2
*(int*)&mem1[offset + 4] = 0; // r3
*(int*)&mem1[offset + 8] = mapInfo3.sVirtualAddr + 0x100; // r4
*(int*)&mem1[offset + 12] = 0; // r5
*(int*)&mem1[offset + 16] = 0x9545b; // r7

Так как он имеет доступ к ключевой информации, мы можем попробовать прочитать ее. Писать "Hello, world!" из защищенного мира, конечно, весело, но трастлет keymaster, определенно, более интересная цель. Другим вариантом постэксплуатации может быть эскалация привилегий до ядра TEE OS и дальше до монитора EL-3, что откроет практически неограниченные возможности по закреплению в системе. Например, в этом посте Gal Beniamini на TEE Qualcomm смог вытащить промежуточные ключи шифрования из защищенного мира и, таким образом, провести offline-атаку на полнодисковое шифрование в Android.

Заключение

При этом со стороны Secure World можно добиться повышения привилегий и в обычной системе Android, которая сама по себе уже не такая легкая цель для поиска и эксплуатации багов. ARM TrustZone и его реализации на различных платформах сейчас довольно актуальная тема для поиска уязвимостей, так как их код еще не так хорошо исследован и содержит ошибки. Кроме этого, например, Samsung имеет программу bug bounty и награждает исследователей за уязвимости TrustZone, в которой мы тоже поучаствовали.

Фаззинг с обратной связью зарекомендовал себя как очень надежный способ поиска уязвимостей. Благодаря таким классным инструментам как AFL и qemu, возможен фаззинг бинарных файлов кастомного формата даже для "неродных" процессорных архитектур. Используйте надежные методы поиска уязвимостей! А с развитием символьного исполнения и инструментов для работы с промежуточным представлением бинарного кода, он станет еще более мощным и универсальным методом.

Полезные ссылки

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть