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

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

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

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

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

Категории

Все категории
Loading categories
ThisSeemsWrong — Разбор и эксплойт для CVE-2024-49746: Parcel::continueWrite в Android закрывает файловые дескрипторы, используемые позже. | Kitploit
Инструменты/GitHubGitHub/michalbednarski/thisseemswrong
Безопасность AndroidФреймворки для эксплойтовАнализ уязвимостейСбор информацииРазработка Полезной НагрузкиЭксплуатация Бинарных Файлов
GitHubmichalbednarski/thisseemswrong

ThisSeemsWrong

Разбор и эксплойт для CVE-2024-49746: Parcel::continueWrite в Android закрывает файловые дескрипторы, используемые позже.

Репозиторий
4715611 месяцев назадПроверено Kitploit

Популярное

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

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

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

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

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

Исправление этой проблемы вышло как CVE-2024-49746: bulletin, patch

«Это выглядит неправильно»

Приведённый выше заголовок — это комментарий из метода Parcel::continueWrite, который на самом деле отвечает за изменение размера объектов Parcel, либо когда пользователь явно запрашивает это (например, через setDataSize()), либо при вызове одного из методов write, когда текущей ёмкости данных недостаточно.```cpp status_t Parcel::continueWrite(size_t desired) { // SNIP: Validate desired size // SNIP: Assign kernelFields & rpcFields from variant member of this class // SNIP: Count number of objects (Binder handles and File Descriptors) // that will be present after resize and assign to objectsSize

if (mOwner) {
    // If the size is going to zero, just release the owner's data.
    if (desired == 0) {
        freeData();
        return NO_ERROR;
    }

    // If there is a different owner, we need to take
    // posession.
    uint8_t* data = (uint8_t*)malloc(desired);
    // SNIP: Check if malloc succeeded
    binder_size_t* objects = nullptr;

    if (kernelFields && objectsSize) {
        objects = (binder_size_t*)calloc(objectsSize, sizeof(binder_size_t));
        // SNIP: Check if calloc succeeded

        // Little hack to only acquire references on objects
        // we will be keeping.
        size_t oldObjectsSize = kernelFields->mObjectsSize;
        kernelFields->mObjectsSize = objectsSize;
        acquireObjects();
        kernelFields->mObjectsSize = oldObjectsSize;
    }
    // SNIP: rpcFields handling for non-/dev/binder Parcels

    if (mData) {
        memcpy(data, mData, mDataSize < desired ? mDataSize : desired);
    }
    if (objects && kernelFields && kernelFields->mObjects) {
        memcpy(objects, kernelFields->mObjects, objectsSize * sizeof(binder_size_t));
    }
    // ALOGI("Freeing data ref of %p (pid=%d)", this, getpid());
    if (kernelFields) {
        // TODO(b/239222407): This seems wrong. We should only free FDs when
        // they are in a truncated section of the parcel.
        closeFileDescriptors();
    }
    mOwner(mData, mDataSize, kernelFields ? kernelFields->mObjects : nullptr,
           kernelFields ? kernelFields->mObjectsSize : 0);
    mOwner = nullptr;

    // SNIP: Allocation count tracking
    // SNIP: Assign data and objects to this object
} else if (mData) {
    // SNIP: Resize data owned by this instance of Parcel
} else {
    // SNIP: Allocate initial data for currently empty Parcel
}

return NO_ERROR;

}

[Когда был добавлен тот комментарий](https://android.googlesource.com/platform/frameworks/native/+/53b6ffe5af3951e8784c451ef8c4ff19f3d6b196%5E!/), вызов `closeFileDescriptors()` был перенесён из `IPCThreadState::freeBuffer()` (который вызывается в приведённом выше коде через указатель на функцию `mOwner()`) в метод `continueWrite()`, однако логика осталась прежней. В конце концов, `Parcel` — ключевая часть Android IPC, и если бы основной IPC закрывал файловые дескрипторы, которые не должен был, это была бы очевидная проблема.

Это подводит нас к важному моменту: когда используется приведённый выше код? Он используется, когда класс `Parcel` принимает владение данными, полученными от Binder-драйвера (которые в этот момент находятся в [`/dev/binder` `mmap`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/binder/ProcessState.cpp;l=587-592;drc=187efe18e3de6258af0230198c881915cc695567) и не могут быть записаны (любые попытки записи в эту память приведут к `SIGSEGV`)), то есть когда `Parcel` является либо входящими данными транзакции (аргумент `data`, передаваемый в [`onTransact()`](https://developer.android.com/reference/android/os/Binder#onTransact(int,%20android.os.Parcel,%20android.os.Parcel,%20int)), либо входящим ответом (то есть объектом `Parcel`, который был передан в вызов [`transact()`](https://developer.android.com/reference/android/os/IBinder#transact(int,%20android.os.Parcel,%20android.os.Parcel,%20int)) как аргумент `reply`; `transact()` устанавливает ссылку внутри этого объекта `Parcel`).

На практике единственный случай, когда мы попадаем в блок `if (mOwner)`, — это когда система [вызывает `setDataSize(0)` для освобождения данных транзакции](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/binder/IPCThreadState.cpp;l=1483-1488;drc=187efe18e3de6258af0230198c881915cc695567), но в этом случае мы также попадаем в `if (desired == 0)`, который выполняет ранний возврат. Однако при легитимном использовании системы нет сценария, при котором мы бы попали в ветку "If there is a different owner, we need to take possession".

# Запуск пути «take possession»

В одном из моих предыдущих эксплойтов я показал [случай, когда `createFromParcel()` может вызвать `writeInt(0)` для `Parcel`, из которого должен читать](https://github.com/michalbednarski/TheLastBundleMismatch#side-effects). Хотя исправление там предотвратило выполнение любых методов `createFromParcel()`, не относящихся к `Intent`, внутри `AccountManagerService`, путь от `createFromParcel()` к `writeInt(0)` остался нетронутым.

Если вкратце, [внутри `PackageParser` есть следующий код](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/PackageParser.java;l=7789-7795;drc=7d3ffbae618e9e728644a96647ed709bf39ae759):```java
final Class<T> cls = (Class<T>) Class.forName(componentName);
final Constructor<T> cons = cls.getConstructor(Parcel.class);

intentsList = new ArrayList<>(N);
for (int i = 0; i < N; ++i) {
    intentsList.add(cons.newInstance(in));
}

Следовательно, мы можем передать объект Parcel, который был передан в createFromParcel, любому доступному в системе public конструктору, принимающему единственный аргумент Parcel

И в другом месте у нас есть следующий код:```java public PooledStringWriter(Parcel out) { mOut = out; mPool = new HashMap<>(); mStart = out.dataPosition(); out.writeInt(0); // reserve space for final pool size. }

Поэтому, чтобы запустить путь «take possession», нам нужен произвольный вызов `readParcelable` для `Parcel`, который был передан в `onTransact()` в качестве `data`; в этом эксплойте я использую для этого [тот же путь, что использовал ранее в другом](https://github.com/michalbednarski/LeakValue#putting-parcelables-in-system_server-and-retrieving-them). Я добиваюсь того, чтобы `readParcelable` вызывал `PackageParser$Activity.CREATOR.createFromParcel()`, который, в свою очередь, читает имя `PooledStringWriter` и вызывает его конструктор; после этого данные `Parcel` заканчиваются, поэтому `writeInt()` должен перераспределить Parcel, попадая в наш путь «take possession».

Здесь стоит отметить: если бы в этот момент данные `Parcel` не заканчивались, `writeInt()` попытался бы перезаписать данные на месте, что в случае данных, поддерживаемых `mmap` из `/dev/binder`, привело бы к `SIGSEGV`.

# File Descriptor Sanitizer
Скачать инструмент