Skip to content
KitploitKITPLOIT
FerramentasExploitsBlog
Log in
Enviar
FerramentasExploitsBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

FeedsContatoPrivacidade© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
ThisSeemsWrong — Artigo e exploit para CVE-2024-49746: Android Parcel::continueWrite fechando File Descriptors que são usados posteriormente | Kitploit
Ferramentas/GitHubGitHub/michalbednarski/thisseemswrong
Segurança AndroidFrameworks de ExploraçãoAnálise de VulnerabilidadesColeta de InformaçõesDesenvolvimento de PayloadsExploração de Binários
GitHubmichalbednarski/thisseemswrong

ThisSeemsWrong

Artigo e exploit para CVE-2024-49746: Android Parcel::continueWrite fechando File Descriptors que são usados posteriormente

Ver Repositório
471514há 1 anoRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

A correção para este problema apareceu como CVE-2024-49746: boletim, correção

"Isto parece errado"

O título acima é o comentário do método Parcel::continueWrite, que é na verdade responsável por redimensionar objetos Parcel, seja quando explicitamente solicitado pelo usuário (por exemplo através de setDataSize()) ou ao chamar um dos métodos write quando a capacidade atual de dados é muito pequena```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;

}

[Quando esse comentário foi introduzido](https://android.googlesource.com/platform/frameworks/native/+/53b6ffe5af3951e8784c451ef8c4ff19f3d6b196%5E!/), a chamada `closeFileDescriptors()` foi movida de `IPCThreadState::freeBuffer()` (que é chamado no código acima através do ponteiro de função `mOwner()`) para o método `continueWrite()`, porém a lógica permaneceu a mesma. Afinal, `Parcel` é parte central do IPC do Android e se o IPC central estivesse fechando Descritores de Arquivo, não deveria ser um problema óbvio.

O que nos leva à parte importante: quando o código acima é usado? Ele é usado quando a classe Parcel transfere a propriedade dos dados recebidos do driver Binder (que nesse ponto residem no [`/dev/binder` `mmap`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/binder/ProcessState.cpp;l=587-592;drc=187efe18e3de6258af0230198c881915cc695567) e não podem ser escritos (qualquer tentativa de escrever nessa memória levaria a `SIGSEGV`)), ou seja, `Parcel` sendo dados de transação recebida (argumento `data` passado para [`onTransact()`](https://developer.android.com/reference/android/os/Binder#onTransact(int,%20android.os.Parcel,%20android.os.Parcel,%20int))) ou resposta recebida (isto é, objeto `Parcel` que foi passado para a chamada [`transact()`](https://developer.android.com/reference/android/os/IBinder#transact(int,%20android.os.Parcel,%20android.os.Parcel,%20int)) como argumento `reply`, `transact()` define uma referência dentro desse objeto `Parcel`).

Na prática, o único caso em que entraríamos no bloco `if (mOwner)` é quando o sistema [chama `setDataSize(0)` para liberar dados de transação](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/binder/IPCThreadState.cpp;l=1483-1488;drc=187efe18e3de6258af0230198c881915cc695567), mas nesse caso também entraríamos em `if (desired == 0)`, que faz um retorno antecipado. Durante o uso legítimo do sistema, não há caso em que entraríamos no caminho "Se há um proprietário diferente, precisamos tomar posse".

# Acionando o caminho "tomar posse"

Em um dos meus exploits anteriores, mostrei [caso em que `createFromParcel()` pode realmente chamar `writeInt(0)` em `Parcel` que deveria estar lendo](https://github.com/michalbednarski/TheLastBundleMismatch#side-effects). Embora a correção lá tenha impedido a execução de qualquer método `createFromParcel()` não-`Intent` dentro de `AccountManagerService`, o caminho de `createFromParcel()` para `writeInt(0)` foi mantido intacto.

Para recapitular, [dentro de `PackageParser` temos o seguinte código](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));
}

Portanto, podemos ter o objeto Parcel que foi passado para createFromParcel passado para qualquer construtor public disponível no sistema que aceita um único argumento Parcel

E em outro lugar temos o seguinte código:```java public PooledStringWriter(Parcel out) { mOut = out; mPool = new HashMap<>(); mStart = out.dataPosition(); out.writeInt(0); // reserve space for final pool size. }

Portanto, para acionar o caminho "take possession", precisamos ter uma chamada `readParcelable` arbitrária em um `Parcel` que foi passado para `onTransact()` como `data`. Neste exploit, estou usando o [mesmo caminho que usei anteriormente em outro](https://github.com/michalbednarski/LeakValue#putting-parcelables-in-system_server-and-retrieving-them). Eu tenho `readParcelable` chamando `PackageParser$Activity.CREATOR.createFromParcel()`, que por sua vez lê o nome `PooledStringWriter` e chama seu construtor, e depois disso os dados do `Parcel` terminam, então `writeInt()` precisa realocar o Parcel, entrando no nosso caminho "take possession"

Vale notar aqui que, se não houvesse fim dos dados do `Parcel` naquele ponto, `writeInt()` tentaria sobrescrever os dados no local, o que, no caso de dados apoiados por `/dev/binder` `mmap`, levaria a `SIGSEGV`

# File Descriptor Sanitizer
Baixar ferramenta