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

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

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

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

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

Категории

Все категории
Loading categories
CVE-2022-20474 — Подробный технический анализ и доказательство концепции для Android CVE-2022-20474 — уязвимости несоответствия Bundle, использующей LazyValue с отрицательной длиной для достижения самоизменяющегося поведения Bundle. | Kitploit
Инструменты/GitHubGitHub/cxxsheng/cve-2022-20474
Безопасность AndroidАнализ уязвимостейЭксплуатацияСтатьи и ИсследованияОбучение и ОбразованиеЭксплуатация Бинарных Файлов
GitHubcxxsheng/cve-2022-20474

CVE-2022-20474

Подробный технический анализ и доказательство концепции для Android CVE-2022-20474 — уязвимости несоответствия Bundle, использующей LazyValue с отрицательной длиной для достижения самоизменяющегося поведения Bundle.

Репозиторий
20141 год назадПроверено Kitploit

Популярное

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

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

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

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

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

CVE-2022-20474 Анализ — Self-changed Bundle в LazyValue

Предисловие

Пожалуйста, прочтите это предупреждение: перед чтением этой статьи следует иметь базовое представление об уязвимостях, связанных с несоответствием Bundle. Если вы еще не читали следующие справочные материалы, рекомендуется сначала прочитать их:

  1. Bundle Фэн-шуй — Подробное объяснение уязвимостей несоответствия сериализации и десериализации в Android: Классическое вводное руководство.
  2. История атак и защиты уязвимостей десериализации Android: Хорошая обзорная статья.
  3. TheLastBundleMismatch: Первая статья о Bundle Mismatch в режиме LazyValue.

Предыстория

Недавно я внимательно изучал статью LeakValue от michalbednarski. В разговоре с Canyie он упомянул, что в этой статье также описывается случай Self-changing Bundle в сценарии LazyValue. Я пошел искать оригинал, и действительно, там был такой абзац, который я упустил, читая статью Михала. В оригинале говорится:

(Also LazyValue with negative length specified can be used (without using other bugs described in this writeup) to create self-changing Bundle, the thing LazyValue was created to eliminate. But that is another story (and separately reported to Google), in this exploit I'm aiming for more)

Михаил, вероятно, имел в виду CVE-2022-20474 (бюллетень, патч). Я взглянул на патч, но функция в ссылке на патч была неполной. Я дополнил её и внимательно изучил:

@@ -4388,6 +4388,9 @@
    public Object readLazyValue(@Nullable ClassLoader loader) {
         int start = dataPosition();
         int type = readInt();
         if (isLengthPrefixed(type)) {
             int objectLength = readInt();
+            if (objectLength < 0) {
+                return null;
+            }
             int end = MathUtils.addOrThrow(dataPosition(), objectLength);
             int valueLength = end - start;
             setDataPosition(end);
             return new LazyValue(this, start, valueLength, type, loader);
         } else {
            return readValue(type, loader, /* clazz */ null);
         }
    }             

objectLength в коде — это length в LazyValue, но на самом деле это только длина изменяемого объекта, содержащегося в LazyValue. Всю длину LazyValue контролирует поле mLength, то есть valueLength в коде, которое в конструкторе LazyValue передаётся в mLength.

Давайте сравним с макетом LazyValue:

       /**
         *                      |   4B   |   4B   |
         * mSource = Parcel{... |  type  | length | object | ...}
         *                      a        b        c        d
         * length = d - c
         * mPosition = a
         * mLength = d - a
         */

Исходя из вышесказанного, можно сделать следующие факты:

  1. mLength представляет всю длину LazyValue: mLength = objectLength + 8 байт.
  2. objectLength должно быть ≥ 0.
  3. Объект LazyValue сохраняет только mLength, но не objectLength, потому что копирование памяти для LazyValue выполняется на основе всего объекта.
  4. После чтения указатель перемещается вперёд; возможно, LazyValue будет прочитан снова.

Затем, после долгих размышлений, мы пришли к выводу, что эти факты нифига не полезны! Потому что на их основе можно изменить значение только при чтении, а мы знаем, что основная идея Self-changed Bundle — изменить его после завершения чтения, чтобы обойти проверку безопасности.

Когда мы уже собирались сдаться, мы внезапно заметили некоторые детали в описании патча:

Addresses a security vulnerability where a (-8) length object would cause dataPosition to be reset back to the statt of the value, and be re-read again.

Аномальный objectLength

Там упоминается, что при objectLength = -8 возникают некоторые проблемы, что дало нам дополнительные подсказки. Может ли LazyValue в этом случае нормально применяться через apply?

        @Override
        public Object apply(@Nullable Class<?> clazz, @Nullable Class<?>[] itemTypes) {
            Parcel source = mSource;
            if (source != null) {
                synchronized (source) {
                    // Check mSource != null guarantees callers won't ever see different objects.
                    if (mSource != null) {
                        int restore = source.dataPosition();
                        try {
                            source.setDataPosition(mPosition);
                            mObject = source.readValue(mLoader, clazz, itemTypes);
                        } finally {
                            source.setDataPosition(restore);
                        }
                        mSource = null;
                    }
                }
            }
            return mObject;
        }

  	/**
     * @see #readValue(int, ClassLoader, Class, Class[])
     */
    @Nullable
    private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz,
            @Nullable Class<?>... itemTypes) {
        int type = readInt();
        final T object;
        if (isLengthPrefixed(type)) {
            int length = readInt();
            int start = dataPosition();
            object = readValue(type, loader, clazz, itemTypes);
            int actual = dataPosition() - start;
            if (actual != length) {
                Slog.wtfStack(TAG,
                        "Unparcelling of " + object + " of type " + Parcel.valueTypeToString(type)
                                + "  consumed " + actual + " bytes, but " + length + " expected.");
            }
        } else {
            object = readValue(type, loader, clazz, itemTypes);
        }
        return object;
    }

Видно, что настоящий readValue начинает чтение с mPosition, затем последовательно читает LazyType и objectLength, после чего переходит к нормальному процессу чтения Value, например, для Parcelable читается ClassName и выполняется createFromParcel. После чтения это ничем не отличается от обычного Key-Value и не влияет на последующую сериализацию. Ещё раз вспомним: основная идея Self-changed Bundle — изменить после завершения чтения. Здесь же просто обычное чтение за пределами границ. Похоже, этот путь не работает.

Что если LazyValue не применяется apply в этом процессе? Другими словами, если он продолжает участвовать в IPC как LazyValue, то будет вызвана его функция writeToParcel:

     public void writeToParcel(Parcel out) {
            Parcel source = mSource;
            if (source != null) {
                synchronized (source) {
                    if (mSource != null) {
                        out.appendFrom(source, mPosition, mLength);
                        return;
                    }
                }
            }
            out.writeValue(mObject);
        }

Весь LazyValue будет скопирован как есть, если только mLength == 0. Стоп! Выше упоминалось, что mLength = objectLength + 8 байт, а из информации о патче следует, что для возникновения уязвимости objectLength должен быть -8, тогда mLength = 0. То есть в этом сценарии весь LazyValue просто исчезает, копируется только String Key, что приводит к пропуску записи. Условие Self-changed Bundle напрямую выполняется.

Теперь, зная причину, мы можем приступить к воспроизведению, но перед этим нам нужно ещё немного деталей.

Деталь 1: Два типа Bundle

 static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L'
 private static final int BUNDLE_MAGIC_NATIVE = 0x4C444E44; // 'B' 'N' 'D' 'N'
Скачать инструмент