Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
OrganizerTransaction — PoC для CVE-2021-39749, позволяющий запускать произвольное Activity на Android 12L Beta | Kitploit
Инструменты/GitHubGitHub/michalbednarski/organizertransaction
Безопасность AndroidАнализ уязвимостейЭксплуатацияПентестинг мобильных приложенийМобильная безопасность
GitHubmichalbednarski/organizertransaction

OrganizerTransaction

PoC для CVE-2021-39749, позволяющий запускать произвольное Activity на Android 12L Beta

Репозиторий

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
37114 лет назадПроверено Kitploit

Это PoC для CVE-2021-39749, который позволяет запускать активности других приложений на Android 12L Beta независимо от их настроек permission и exported

В Android 12L доступ к TaskFragmentOrganizer (намеренно) больше не требует разрешения MANAGE_ACTIVITY_TASKS

Использование приложения, представленного здесь, требует отключения проверок скрытых API, это можно сделать через adb shell settings put global hidden_api_policy 1. Это не является границей безопасности, и существуют известные обходные пути на уровне приложений

Вот коммиты, исправляющие эту ошибку (и несколько связанных, упомянутых в оригинальном отчёте):

  1. startActivityInTaskFragment больше не полагается на Binder.getCallingUid()
  2. ResolverActivity теперь имеет включённый relinquishTaskIdentity
  3. (Не требуется для запуска других активностей, но позволяет перемещать их по экрану, делать их прозрачными и уязвимыми для tap-jacking) SurfaceControl для TaskFragment больше не предоставляется
  4. (Не показано в коде здесь, проблема только упомянута в оригинальном отчёте) Решение о том, отправлять ли ActivityRecord#appToken в TaskFragmentOrganizer, теперь основано на uid, а не на pid

Вы можете переключиться на android-12.1.0_r4, откатить первые 3 коммита (или первые 2, приложение всё равно сможет выключить устройство (запустив ShutdownActivity), но флажок "Zoom and set alpha" не будет работать)

(Первый коммит из этого списка будет иметь конфликты слияния в тестах, если вы попытаетесь его откатить, но их можно игнорировать)

Binder.getCallingUid(), который всегда возвращает системный uid

Метод Binder.getCallingUid() возвращает uid процесса, отправившего текущую обрабатываемую Binder-транзакцию. Этот uid хранится в переменной, локальной для потока. Код, обрабатывающий транзакцию, может вызвать Binder.clearCallingIdentity(), чтобы установить эту переменную в uid собственного процесса, указывая методам, вызываемым позже во время обработки транзакции, что проверки разрешений должны выполняться против самого себя (кода, обрабатывающего транзакцию), а не вызывающего Binder-транзакцию

Иногда встречаются вызовы Binder.getCallingUid(), которые всегда вызываются после Binder.clearCallingIdentity(), и поэтому всегда возвращают uid собственного процесса. Иногда это происходит намеренно, например, в ActivityTaskManagerService#startDreamActivity (хотя это довольно запутанный способ сделать Process.myUid() или Os.getuid())

Я написал для себя (основанный на Soot) инструмент статического анализа, который сообщает о таких вызовах Binder.getCallingUid() (и других проверках разрешений), которые могут произойти только после Binder.clearCallingIdentity(). (У меня есть собственная логика обработки Jimple/Shimple IR, предоставляемого Soot, хотя, возможно, есть лучший способ сделать это с помощью Soot, но это то, что у меня есть сейчас)

В Android 12L Beta этот инструмент нашёл один такой вызов в ActivityStartController#startActivityInTaskFragment (Примечание: исходный код тогда не был доступен, так как Beta-релизы не являются открытым исходным кодом, но Shimple в целом читаем, поэтому я использовал Soot также как декомпилятор Java)

Как вызвать startActivityInTaskFragment

В рамках отчёта статического анализа я получил иерархию вызовов от реализации onTransact() (где начинается Binder-вызов) до startActivityInTaskFragment:

  1. onTransact в сгенерированном aidl-коде IWindowOrganizerController
  2. WindowOrganizerController#applyTransaction (без аргумента CallerInfo)
  3. WindowOrganizerController#applyTransaction (с аргументом CallerInfo)
  4. WindowOrganizerController#applyHierarchyOp
  5. ActivityStartController#startActivityInTaskFragment

Я обнаружил, что Binder-вызовы applyTransaction присутствуют в классе TaskFragmentOrganizer, и решил использовать его как более удобную обёртку, чем выполнять все Binder-вызовы напрямую (ни то, ни другое не является публичным API, поэтому в любом случае пришлось использовать рефлексию)

Прежде всего, метод "2." вызывает enforceTaskPermission, который на Android 12.0 проверял разрешение MANAGE_ACTIVITY_TASKS только для signature, которое мы не могли получить, однако на Android 12L правила были смягчены, так что определённые транзакции можно выполнять без разрешений. Оказалось, что ни одна из операций, необходимых для выполнения startActivityInTaskFragment, не требовала разрешения (если с транзакцией был связан TaskFragmentOrganizer)

Итак, мы хотим выполнить HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT. Для этого наш TaskFragment должен быть зарегистрирован в mLaunchTaskFragments, иначе будет сообщено об исключении "Not allowed to operate with invalid fragment token"

Мы можем зарегистрировать такой TaskFragment через HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT, который вызывает createTaskFragment()

(В коде PoC эти транзакции отправляются в SecondActivity: HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT отправляется initOrganizerAndFragment(), а HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT отправляется startActivityInOrganizer)

Это позволяет нам вызывать startActivityInTaskFragment, и Intent'ы активностей, запущенных здесь, считаются исходящими от системного uid, но оказывается, что само по себе это не даёт нам ничего: активности, запущенные системой, не могут выполнять URI grants, и если мы попытаемся запустить активность другого приложения, нас остановит проверка canEmbedActivity

Обход canEmbedActivity

Давайте снова посмотрим на canEmbedActivity: встраивание разрешено, если taskFragment.getTask().effectiveUid является uid системы или совпадает с uid запускаемого приложения. Нам нужно оказаться в задаче, чей effectiveUid является системным

Также вернёмся к createTaskFragment(): создание TaskFragment разрешалось только если rootActivity.getUid() != ownerActivity.getUid(). Это означает, что наша активность должна находиться внизу back-stack'а задачи, в которой она находится

Нам нужно будет запустить новую Task (через Intent.FLAG_ACTIVITY_NEW_TASK), в которой будет активность, принадлежащая системному uid (так что Task#effectiveUid будет установлен в AID_SYSTEM), а затем эта активность запустит нашу активность (в той же задаче) и finish() сама себя (так что наша активность станет корневой в этой задаче, что позволит нам использовать createTaskFragment())

Одной из таких активностей является ChooserActivity. Chooser обычно используется для выбора приложения, которое пользователь хочет использовать после выбора опции "поделиться". ChooserActivity, однако, имеет android:relinquishTaskIdentity="true" в AndroidManifest.xml, что означает, что когда она запускает другую активность, она перезаписывает Task#effectiveUid на uid только что запущенного приложения

(relinquishTaskIdentity работает только при использовании первым приложением в задаче и только для системных приложений, поэтому мы не можем сами использовать relinquishTaskIdentity и запустить системное приложение, чтобы перезаписать Task#effectiveUid нашей задачи)

Другой такой активностью (которая может запустить нашу активность и finish() сама себя) является ResolverActivity. Она используется при запуске неявного Intent, который разрешается в несколько активностей. Resolver (в отличие от Chooser) предлагает опцию запомнить выбор, что позволяет вам (как пользователю телефона) отличить эти две активности. ResolverActivity не имела установленного relinquishTaskIdentity, однако Resolver использует собственный Intent для поиска доступных опций (в то время как Chooser берёт Intent, предоставленный в Extras). Это оказывается проблемой для эксплуатации, потому что флаги Intent, используемые Resolver при запуске выбранной активности, будут такими же, как и те, что использовались для запуска Resolver, и:

  • Если мы не установим Intent.FLAG_ACTIVITY_NEW_TASK, Resolver будет запущен в нашей Task, которая уже имеет постоянно установленный effectiveUid
  • Если мы установим Intent.FLAG_ACTIVITY_NEW_TASK, Resolver запустит выбор в ещё одну Task, которая затем будет иметь effectiveUid, установленный на uid запущенного приложения

Решение этих проблем — использовать оба варианта:

  1. Сначала мы запускаем ChooserActivity: Мы предоставляем в её Intent:
    • Intent.FLAG_ACTIVITY_NEW_TASK, чтобы Chooser был запущен в новой задаче (которая будет иметь effectiveUid системы, но только до следующего запуска активности)
    • Intent.EXTRA_INTENT, установленный на Intent, который не соответствует ни одной активности, и единственные оставшиеся опции в Chooser будут из Intent.EXTRA_INITIAL_INTENTS
    • Intent.EXTRA_INITIAL_INTENTS, содержащий массив с одним элементом: Intent, который мы хотим, чтобы Chooser запустил (когда есть только одна опция, и Chooser, и Resolver пропускают запрос и немедленно запускают единственную опцию и finish() сами себя)
  2. Затем ResolverActivity запускается ChooserActivity:
    • Resolver не имел установленного , поэтому теперь установлен на систему и останется таким независимо от следующих активностей, запущенных в этой задаче

(В PoC-приложении подготовка этих шагов выполняется в FirstActivity)

Другие трюки с TaskFragmentOrganizer

TaskFragmentOrganizer получает SurfaceControl через колбэк onTaskFragmentAppeared, и, используя этот SurfaceControl, можно масштабировать запущенную активность и делать её прозрачной, при этом она всё равно будет получать события касания и не будет считаться скрытой (поэтому элементы, защищённые от tap-jacking, всё равно можно нажимать)

Вы можете увидеть это, отметив флажок "Zoom and set alpha" в PoC-приложении

Это исправлено коммитом "3." из списка исправлений вверху


Ещё одна вещь: ActivityRecord#appToken-ы активностей, работающих внутри TaskFragment, передаются в колбэки TaskFragmentOrganizer. Этот список фильтровался, чтобы включать только токены активностей в том же процессе, однако проверка выполнялась путём сравнения pid TaskFragmentOrganizer с pid активности, чей appToken мы могли получить. Я на самом деле не проверял, но думаю, что приложение могло бы создать TaskFragmentOrganizer, выйти из процесса, использованного для его первоначального создания, и дождаться, пока его pid будет переиспользован как pid активности другого приложения, чтобы получить его appToken. Вот коммит ("4." в списке исправлений выше), который переключает проверку с pid-основанной на uid-основанную (похоже, этот коммит был сделан независимо от моего отчёта (хотя и после него))

Как только злоумышленник получает appToken активности, он может внедрять вызовы onActivityResult() (даже если целевое приложение само не вызывало startActivityForResult()) и, возможно, подделывать savedInstanceState (вызывая activityStopped(), при условии, что злоумышленник может выиграть гонку с целевым приложением, вызывающим этот метод, и дополнительный вызов не приведёт к потере состояния из-за сбоя)

Я не проверял, можно ли это сделать в данном случае, однако ранее, с CVE-2020-0001 (Да, мне достался красивый номер), я смог использовать подделку savedInstanceState и внедрение onActivityResult() для обмана системного приложения настроек, заставив его включить мой AccessibilityService без взаимодействия с пользователем, но это история для другого раза

Скачать инструмент
relinquishTaskIdentity
Task#effectiveUid
  • Intent не имеет Intent.FLAG_ACTIVITY_NEW_TASK, поэтому следующая активность запускается в той же задаче
  • Действие Intent установлено на нестандартное, соответствующее только <intent-filter>, который мы объявили сами в нашем приложении, поэтому Resolver немедленно переходит к запуску нашей активности
  • ResolverActivity запускает нашу активность
    • Теперь мы находимся в задаче, чей effectiveUid равен AID_SYSTEM, поэтому canEmbedActivity() разрешает всё
    • И Chooser, и Resolver завершили сами себя, поэтому мы являемся корневой активностью в задаче и можем использовать createTaskFragment()