Skip to content
KitploitKITPLOIT
OutilsExploitsBlog
Log in
Soumettre
OutilsExploitsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
ThisSeemsWrong — Analyse détaillée et exploitation pour CVE-2024-49746 : Parcel::continueWrite d'Android ferme des descripteurs de fichiers qui sont ensuite utilisés | Kitploit
Outils/GitHubGitHub/michalbednarski/thisseemswrong
Sécurité AndroidFrameworks d'ExploitationAnalyse des VulnérabilitésCollecte d'InformationsDéveloppement de Charges UtilesExploitation de Binaires
GitHubmichalbednarski/thisseemswrong

ThisSeemsWrong

Analyse détaillée et exploitation pour CVE-2024-49746 : Parcel::continueWrite d'Android ferme des descripteurs de fichiers qui sont ensuite utilisés

Voir le dépôt
47156il y a 11 moisVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Le correctif pour ce problème est apparu sous le nom CVE-2024-49746 : bulletin, correctif

"Cela semble faux"

Le titre ci-dessus est le commentaire de la méthode Parcel::continueWrite, qui est en réalité responsable du redimensionnement des objets Parcel, soit lorsqu'il est explicitement demandé par l'utilisateur (par exemple via setDataSize()) ou lors de l'appel à l'une des méthodes write lorsque la capacité actuelle des données est trop faible```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;

}

[Lorsque ce commentaire a été introduit](https://android.googlesource.com/platform/frameworks/native/+/53b6ffe5af3951e8784c451ef8c4ff19f3d6b196%5E!/), l'appel à `closeFileDescriptors()` a été déplacé de `IPCThreadState::freeBuffer()` (qui est appelé dans le code ci-dessus via le pointeur de fonction `mOwner()`) vers la méthode `continueWrite()`, mais la logique est restée la même. Après tout, `Parcel` est une partie centrale de l'IPC Android et si l'IPC central fermait des descripteurs de fichiers qu'il ne devrait pas, ce serait un problème évident

Ce qui nous amène à la partie importante : quand le code ci-dessus est-il utilisé ? Il est utilisé lorsque la classe Parcel transfère la propriété des données reçues du pilote Binder (qui se trouvent à ce moment-là dans le [`mmap` de `/dev/binder`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/binder/ProcessState.cpp;l=587-592;drc=187efe18e3de6258af0230198c881915cc695567) et ne peuvent pas être écrites (toute tentative d'écriture de cette mémoire entraînerait un `SIGSEGV`)), c'est-à-dire que `Parcel` est soit des données de transaction entrantes (l'argument `data` passé à [`onTransact()`](https://developer.android.com/reference/android/os/Binder#onTransact(int,%20android.os.Parcel,%20android.os.Parcel,%20int))) soit une réponse entrante (c'est-à-dire l'objet `Parcel` qui a été passé à l'appel [`transact()`](https://developer.android.com/reference/android/os/IBinder#transact(int,%20android.os.Parcel,%20android.os.Parcel,%20int)) comme argument `reply`, `transact()` définit une référence dans cet objet `Parcel`)

En pratique, le seul cas où l'on entre dans le bloc `if (mOwner)` est lorsque le système [appelle `setDataSize(0)` pour libérer les données de transaction](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/binder/IPCThreadState.cpp;l=1483-1488;drc=187efe18e3de6258af0230198c881915cc695567), mais dans ce cas, on entre aussi dans `if (desired == 0)` qui effectue un retour prématuré. Lors d'une utilisation légitime du système, il n'y a aucun cas où l'on emprunterait le chemin « S'il y a un propriétaire différent, nous devons prendre possession ».

# Déclencher le chemin « prendre possession »

Dans l'un de mes exploits précédents, j'ai montré [un cas où `createFromParcel()` peut en fait appeler `writeInt(0)` sur un `Parcel` qu'il est censé lire](https://github.com/michalbednarski/TheLastBundleMismatch#side-effects). Bien que le correctif ait empêché l'exécution de toute méthode `createFromParcel()` non-`Intent` dans `AccountManagerService`, le chemin de `createFromParcel()` vers `writeInt(0)` a été conservé intact.

Pour récapituler, [dans `PackageParser`, nous avons le code suivant](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));
}

Par conséquent, nous pouvons avoir un objet Parcel qui a été passé à createFromParcel passé à tout constructeur public disponible dans le système qui accepte un seul argument Parcel

Et ailleurs nous avons le code suivant:```java public PooledStringWriter(Parcel out) { mOut = out; mPool = new HashMap<>(); mStart = out.dataPosition(); out.writeInt(0); // reserve space for final pool size. }

Par conséquent, pour déclencher le chemin « take possession », nous devons avoir un appel arbitraire à `readParcelable` sur le `Parcel` qui a été passé à `onTransact()` en tant que `data`. Dans cet exploit, j'utilise pour cela le [même chemin que j'ai précédemment utilisé dans un autre](https://github.com/michalbednarski/LeakValue#putting-parcelables-in-system_server-and-retrieving-them). J'ai l'appel `readParcelable` à `PackageParser$Activity.CREATOR.createFromParcel()`, qui à son tour lit le nom de `PooledStringWriter` et appelle son constructeur, et après cela les données du `Parcel` se terminent, donc `writeInt()` doit réallouer le Parcel, entrant ainsi dans notre chemin « take possession »

À noter ici, s'il n'y avait pas de fin des données `Parcel` à ce moment-là, `writeInt()` tenterait de réécrire les données sur place, ce qui, dans le cas de données soutenues par `/dev/binder` `mmap`, mènerait à un `SIGSEGV`

# File Descriptor Sanitizer
Télécharger l’outil