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

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

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

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

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

Категории

Все категории
Loading categories
TheLastBundleMismatch — Writeup и эксплойт для CVE-2023-45777, обход проверки Intent внутри AccountManagerService на Android 13, несмотря на смягчение "Lazy Bundle" | Kitploit
Инструменты/GitHubGitHub/michalbednarski/thelastbundlemismatch
Безопасность AndroidАнализ уязвимостейЭксплуатацияАнализ Бинарных ФайловСтатьи и Исследования
GitHubmichalbednarski/thelastbundlemismatch

TheLastBundleMismatch

Writeup и эксплойт для CVE-2023-45777, обход проверки Intent внутри AccountManagerService на Android 13, несмотря на смягчение "Lazy Bundle"

Репозиторий

Популярное

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

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

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

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

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

Загадочный патч

Начнём на этот раз с патча, который появился как исправление для CVE-2023-45777 в Android Security Bulletin:```diff diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 7a19d034c2c8..5238595fe2a2 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4923,7 +4923,7 @@ public class AccountManagerService p.setDataPosition(0); Bundle simulateBundle = p.readBundle(); p.recycle();

  •        Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
    
  •        Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
           if (intent != null && intent.getClass() != Intent.class) {
               return false;
           }
    
Few people were puzzled by it enough to ask me, previously I've replied to them with some hints and now I'm publishing full writeup for this issue

But first lets provide some context about what is going on in this patch

This is change in [`checkKeyIntent()` method](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=4938-4954;drc=47de64a38aa1799cb41f41b2ea0c539ee61de64d). This method performs multiple checks to ensure that `Intent` provided by application is safe for system to launch (using privileges of system)

First, this method uses `checkKeyIntentParceledCorrectly()` which serializes and deserializes again `Bundle` which we're checking and checks if `Intent` taken from `Bundle` before that matches `Intent` from `Bundle` after such cycle. Since launch of `Intent` happens in other system app processes than one which performs validation, it was previously [possible to construct `Bundle`-s which appeared safe during validation inside `AccountManagerService`, but contained different `Intent` after being sent to next process](https://github.com/michalbednarski/IntentsLab/issues/2#issuecomment-344365482). This simulates sending `Bundle` to next process in order to detect such situations.

After `checkKeyIntentParceledCorrectly()` we have `bundle.getParcelable()` call, which this patch switches from deprecated version that could construct any object to one that validates that object that is about to be deserialized is of type which was specified in second parameter

That version with type parameter was introduced in Android 13, as part of larger `Parcel`/`Bundle` hardening. In particular, before Android 13 when `Bundle` was sent between processes, it kept raw copy of whole serialized data until any item was accessed, at which point every value was deserialized. Now when any value is accessed for first time after `Bundle` has been received, only `String` keys and the values of primitive types are deserialized, while non-primitive values are left as `LazyValue`-s, which have their length stored as part of serialized data in order to ensure that even when serialization/deserialization logic is mismatched, such mismatches won't affect other entries

Before we dive in, lets have a look at `LazyValue`: In it's source code [we've got nice comment explaining it's data structure](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=4392-4399;drc=03c34f57c05feecfb090de3917787f049cb5f804)```
                     |   4B   |   4B   |
mSource = Parcel{... |  type  | length | object | ...}
                     a        b        c        d
length = d - c
mPosition = a
mLength = d - a

mPosition и mLength описывают расположение всех данных LazyValue в исходном Parcel, включая type и length. "length" (без "m" в начале) относится к значению длины, записанному в Parcel, и исключает заголовок (type и length)

Если Bundle, содержащий LazyValue, пересылается в другой процесс, весь LazyValue, включая поля type и length, копируется дословно из Bundle.mParcelledData в целевой Parcel

Когда элемент Bundle, представленный LazyValue, запрашивается, Parcel перематывается к mPosition и вызывается readValue(). Если в bundle.getParcelable() передаётся аргумент типа, он передаётся в readValue(), который как гарантирует, что тип, подлежащий распаковке, является ожидаемым, так и проверяет после распаковки, что тип распакованного значения является ожидаемым. После распаковки LazyValue заменяется, поэтому при следующей записи Bundle в Parcel значение будет сериализовано через writeValue() снова

Использование типизированного параметра Bundle.get*()/Parcel.read*() в основном актуально для таких методов, как Parcel.readParcelableList(), который возвращает ArrayList, и из-за стирания типов в Java, даже если вы сделали что-то вроде List<SomeParcelableType> field = parcel.readParcelableList();, часть <SomeParcelableType> не проверялась во время выполнения, и такой List мог содержать любые классы Parcelable, доступные в системе, и поэтому все createFromParcel/writeToParcel, доступные в системе, могли использоваться как часть сериализации/десериализации типа, содержащего такой List

Вам также может быть интересна презентация команды Android Security and Privacy о внедрении этих механизмов (слайды, видео)

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

Побочные эффекты

Взгляните ещё раз на патч с самого начала

  • Если значение, десериализованное под ключом "intent", является Intent
    • Оно будет проверено на соответствие компоненту, который системе безопасно запускать
    • Если бы несоответствие можно было спровоцировать из объекта Intent, у нас была бы гораздо более серьёзная проблема
  • Если десериализуемое значение не является Intent
    • Чтобы сделать что-то плохое, нам нужно было бы иметь Intent внутри Bundle после его отправки в другой процесс, но тип Parcelable сохраняется на более раннем смещении, чем любое возможное несоответствие, а префикс длины LazyValue не позволяет нам изменять следующие пары ключ-значение в случае несоответствия writeToParcel/createFromParcel

Итак, что опасного мог бы здесь сделать вызов bundle.getParcelable(AccountManager.KEY_INTENT) без аргумента типа?

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

Ответ заключается в вызове несвязанного createFromParcel(), который фактически изменяет необработанные данные LazyValue, хранящегося под другим ключом и передаваемого дословно в следующий процесс

Скачать инструмент