Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
ReparcelBug2 — Writeup ed exploit per l'app installata per l'escalation dei privilegi di sistema su Android 12 Beta tramite CVE-2021-0928, una discrepanza di serializzazione `writeToParcel`/`createFromParcel` in `OutputConfiguration` | Kitploit
Strumenti/GitHubGitHub/michalbednarski/reparcelbug2
Sicurezza AndroidEscalation di PrivilegiAnalisi delle VulnerabilitàExploitSicurezza MobileAnalisi di Binari
GitHubmichalbednarski/reparcelbug2

ReparcelBug2

Writeup ed exploit per l'app installata per l'escalation dei privilegi di sistema su Android 12 Beta tramite CVE-2021-0928, una discrepanza di serializzazione `writeToParcel`/`createFromParcel` in `OutputConfiguration`

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi
Vedi Repository
123234 anni faRevisionato da Kitploit

CVE-2021-0928, mancata corrispondenza di serializzazione writeToParcel/createFromParcel in android.hardware.camera2.params.OutputConfiguration

Questo è un exploit che utilizza quella vulnerabilità per l'escalation dei privilegi da un'app Android installata all'app Impostazioni di Android (o qualsiasi altra app installata a cui l'app potesse inviare un <receiver> dichiarato in AndroidManifest.xml; l'escalation dei privilegi inviando a un <activity> era possibile anche se non presentata qui)

Ho trovato il problema originariamente su Android 12 Developer Preview 3

La versione dell'exploit presente in questo repository funziona su Android 12 Beta 2 e 3

La vulnerabilità è stata corretta nella prima release ufficiale di Android 12

Il writeup qui sotto è stato originariamente scritto per Google per la considerazione di questo report come catena di exploit completa

Al momento della scrittura, Android 12 non era disponibile in AOSP (le release Android Developer Preview/Beta non sono open source)

Screenshot della notifica Android dall'app Impostazioni: Hello from uid=1000(system) gid=1000(system) groups=1000(system),1007(log),1065(reserved_disk),1077(external_storage),3001(net_bt_admin),3002(net_bt),3003(inet),3007(net_bw_acct),9997(everybody) context=u:r:system_app:s0

Introduzione a Parcel

La maggior parte dell'IPC su Android viene effettuata tramite la classe chiamata Parcel

L'uso di base di Parcel è il seguente:```java Parcel p = Parcel.obtain(); p.writeInt(1); p.writeString("Hello");

root@kitploit:~
Then `Parcel` viene inviato a un altro processo [tramite `Binder`](https://developer.android.com/reference/android/os/IBinder#transact(int,%20android.os.Parcel,%20android.os.Parcel,%20int)). In alternativa, per testare, si può chiamare [`p.setDataPosition(0)`](https://developer.android.com/reference/android/os/Parcel#setDataPosition(int)) per riportare il parcel alla posizione iniziale e iniziare a leggere:```java
int a = p.readInt(); // a = 1
String b = p.readString(); // b = "Hello"

Va notato che Parcel internamente mantiene la posizione da cui vengono eseguite le letture. È responsabilità dell'utente della classe Parcel assicurarsi che i metodi read* corrispondano ai metodi write* utilizzati in precedenza, altrimenti le letture successive avverranno da posizioni errate nel buffer

Parcel offre anche la possibilità di scrivere oggetti personalizzati, il modo preferito per farlo è implementando l'interfaccia Parcelable

Ecco un esempio di implementazione dell'interfaccia Parcelable (codice irrilevante rimosso, la classe WindowContainerTransaction viene utilizzata nell'exploit come parte della catena di gadget, tuttavia non c'è nulla di sbagliato in essa)```java package android.window; public final class WindowContainerTransaction implements Parcelable { private final ArrayMap<IBinder, Change> mChanges = new ArrayMap<>(); private final ArrayList mHierarchyOps = new ArrayList<>();

root@kitploit:~
private WindowContainerTransaction(Parcel in) {
    in.readMap(mChanges, null /* loader */);
    in.readList(mHierarchyOps, null /* loader */);
}

@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
    dest.writeMap(mChanges);
    dest.writeList(mHierarchyOps);
}

@NonNull
public static final Creator<WindowContainerTransaction> CREATOR =
        new Creator<WindowContainerTransaction>() {
            @Override
            public WindowContainerTransaction createFromParcel(Parcel in) {
                return new WindowContainerTransaction(in);
            }
        };

}

root@kitploit:~
Come si può vedere sopra, il metodo `writeToParcel()` viene utilizzato durante la scrittura. Poi, durante la lettura, viene chiamato il metodo factory `CREATOR.createFromParcel()`. È responsabilità dell'implementazione di `Parcelable` garantire che `createFromParcel` legga la stessa quantità di dati che è stata scritta da `writeToParcel`, altrimenti tutte le letture successive da quella `Parcel` leggeranno dati da un offset errato

Tale classe può essere scritta/letta da/verso `Parcel` tramite:

* Chiamando direttamente `obj.writeToParcel(parcel, 0)` / `obj = WindowContainerTransaction.CREATOR.createFromParcel()`, questo viene spesso usato quando il tipo della classe è noto, ad esempio quando `Parcelable` ha un campo con un diverso `Parcelable` o nel codice generato da AIDL quando il metodo RPC definito ha un `Parcelable` come argomento
* Tramite `Parcel.writeParcelable`/`readParcelable`. [`writeParcelable`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=1909;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3) scrive prima il nome della classe e poi chiama il metodo `writeToParcel` dell'interfaccia `Parcelable`. [`readParcelable`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=3282;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3) legge il nome della classe scritto, trova la classe con quel nome nel `ClassLoader` fornito o in `BOOTCLASSPATH` se è stato fornito null. Una volta trovata la classe, il suo campo statico `CREATOR` viene usato per ottenere un'istanza di [`Parcelable.Creator`](https://developer.android.com/reference/android/os/Parcelable.Creator) che è la factory usata per leggere quella classe. È importante notare che quando viene usato il metodo `readParcelable` può leggere qualsiasi `Parcelable` disponibile nel class path poiché il nome dell'oggetto da creare viene letto dalla stessa `Parcel`
* `readParcelable` è usato da molti altri metodi di `Parcel`, ad esempio `readList` visto nell'esempio sopra legge gli elementi tramite `readValue`, che è il metodo più generico per trasferire oggetti in `Parcel` e uno dei modi in cui opera è tramite `readParcelable`. Inoltre, nell'esempio sopra, a causa della Type Erasure di Java, il campo `ArrayList<HierarchyOp> mHierarchyOps` può in realtà contenere qualsiasi oggetto supportato da Parcel, non solo quelli compatibili con il tipo specificato nella dichiarazione del tipo generico

# Mismatch `writeToParcel`/`createFromParcel`

Come notato sopra, è responsabilità dell'implementazione dell'interfaccia `Parcelable` garantire che `createFromParcel` legga dalla `Parcel` la stessa quantità di dati che il corrispondente `writeToParcel` ha precedentemente scritto. Ogni volta che in `BOOTCLASSPATH` c'è un `Parcelable` che può violare questo contratto, si crea una vulnerabilità poiché consente il seguente scenario:

1. Un'applicazione malintenzionata invia a `system_server` un `Bundle` OPPURE un `Parcelable` contenente un'istanza `Parcelable` difettosa insieme a dati costruiti appositamente che verranno effettivamente letti nel passaggio 3 ma passati verbatim durante il passaggio 2
2. `system_server` verifica che il `Bundle` sia sicuro e poi lo inoltra OPPURE `system_server` passa il `Parcelable` fornito a un metodo AIDL che ha anche dati critici passati nel parametro successivo (se i dati ricevuti in quel parametro potessero essere modificati, ciò causerebbe un problema di sicurezza)
3. Un'altra app riceve dati da `system_server` e si fida di essi, tuttavia a causa della serializzazione difettosa i dati che vede effettivamente differiscono da quelli che `system_server` intendeva inviare

Ho usato "OPPURE" nei passaggi sopra poiché questi descrivono sia una [vecchia variante di exploit che porta all'avvio di un'Activity arbitraria che ho pubblicato nel 2017](https://github.com/michalbednarski/ReparcelBug) (sul lato sinistro di "OPPURE") sia una nuova variante che descriverò qui nella prossima sezione

# Come viene eseguito `BroadcastReceiver` nell'app

Dal punto di vista dello sviluppatore di applicazioni che usa le API disponibili nell'Android SDK, il modo in cui funziona [`BroadcastReceiver`](https://developer.android.com/guide/components/broadcasts) è che un'applicazione chiama [`sendBroadcast`](https://developer.android.com/reference/android/content/Context#sendBroadcast(android.content.Intent)) (anche se spesso le app vogliono ricevere Broadcast dal sistema, non inviarli) e poi l'Intent trasmesso viene confrontato con i `<receiver>` definiti in `AndroidManifest.xml`; quando ciò accade, il sistema avvia il processo dell'applicazione ricevente, istanzia la sottoclasse di `BroadcastReceiver` come definita nell'attributo `<receiver android:name>` e poi chiama il metodo [`onReceive`](https://developer.android.com/reference/android/content/BroadcastReceiver#onReceive(android.content.Context,%20android.content.Intent))

Diamo un'occhiata alla comunicazione con `system_server` che avviene nel processo che riceve il broadcast:

* Quando il processo dell'applicazione viene inizialmente avviato, [chiama `IActivityManager.attachApplication()`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=7340;drc=f53e23b917aa0f6a6310e46a233a29b6d6226b2c), facendo così passa l'handle di [`IApplicationThread`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/IApplicationThread.aidl) che viene usato dal sistema per dire al processo dell'applicazione cosa fare
* Quando il sistema vuole eseguire un `BroadcastReceiver` registrato nel manifest nel processo dell'applicazione, chiama il metodo [`scheduleReceiver`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=950;drc=f53e23b917aa0f6a6310e46a233a29b6d6226b2c) usando `IApplicationThread` descritto nel punto precedente. Questo metodo ha più argomenti ma qui i più importanti sono i primi due:
  1. `Intent intent`, che è stato precedentemente passato al sistema quando è stata chiamata `sendBroadcast()`
  2. `ActivityInfo info`, che contiene informazioni sul componente che deve essere eseguito. Il valore di questo parametro viene preso dal sistema dal Package Manager Service. La cosa più importante è che i dati passati in questo parametro includono il percorso del file da cui verrà caricata la classe Java che gestisce il broadcast ricevuto

A questo punto probabilmente puoi indovinare qual è questo nuovo percorso di exploit: chiamare `sendBroadcast()` passando un `Intent` che farà sì che quando il sistema tenta di chiamare `scheduleReceiver`, l'applicazione in cui viene invocato `scheduleReceiver` vedrà un `ActivityInfo` manomesso

Va notato che questo nuovo percorso di exploit è diventato possibile in Android 12 poiché in precedenza non c'era modo di inserire `Parcelable` arbitrari in un `Intent` ([gli extra di Intent](https://developer.android.com/reference/android/content/Intent#putExtra(java.lang.String,%20android.os.Parcelable)) non contano poiché vengono inseriti in un `Bundle` la cui intera lunghezza viene scritta nella Parcel e viene [letto come un unico blob](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/BaseBundle.java;l=1671-1681;drc=5d123b67756dffcfdebdb936ab2de2b29c799321), quindi gli extra non possono causare un'errata interpretazione dell'oggetto `Intent` che li contiene)

# Attivazione del mismatch `writeToParcel`/`createFromParcel`

La maggior parte delle volte i mismatch `writeToParcel`/`createFromParcel` sono casi in cui in uno di questi metodi uno dei campi viene dimenticato o scritto due volte; in tal caso, inviare tale oggetto attiverà sempre il mismatch. (La maggior parte delle volte ciò accade quando l'oggetto, pur essendo `Parcelable`, non viene realmente usato tra processi, altrimenti sarebbe stato notato rapidamente durante l'uso normale)

Questa volta però non è stato così e attivare il mismatch non è ovvio

Diamo un'occhiata alla classe vulnerabile ([l'originale era qui](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/hardware/camera2/params/OutputConfiguration.java;drc=46c390a1c695e2dc458cb889e40559f259f60aed), le righe marcate `// New in Android 12` sono state aggiunte manualmente poiché non erano presenti in AOSP al momento della scrittura) ([Ecco il commit che ha introdotto originariamente la vulnerabilità](https://android.googlesource.com/platform/frameworks/base/+/a69b1bc58b0838e06deefb190e226774a34671e6%5E%21/#F10), tuttavia è stato pubblicato dopo il rilascio di Android 12)```java
package android.hardware.camera2.params;

public final class OutputConfiguration implements Parcelable {
    private OutputConfiguration(@NonNull Parcel source) {
        int rotation = source.readInt();
        int surfaceSetId = source.readInt();
        int surfaceType = source.readInt();
        int width = source.readInt();
        int height = source.readInt();
        boolean isDeferred = source.readInt() == 1;
        boolean isShared = source.readInt() == 1;
        ArrayList<Surface> surfaces = new ArrayList<Surface>();
        source.readTypedList(surfaces, Surface.CREATOR);
        String physicalCameraId = source.readString();
        boolean isMultiResolution = source.readInt() == 1; // New in Android 12
        ArrayList<Integer> sensorPixelModesUsed = new ArrayList<Integer>(); // New in Android 12
        source.readList(sensorPixelModesUsed, Integer.class.getClassLoader()); // New in Android 12

		// SNIP: copy values from variables set above to fields of this class
    }

    public static final @android.annotation.NonNull Parcelable.Creator<OutputConfiguration> CREATOR =
            new Parcelable.Creator<OutputConfiguration>() {
        @Override
        public OutputConfiguration createFromParcel(Parcel source) {
            try {
                OutputConfiguration outputConfiguration = new OutputConfiguration(source);
                return outputConfiguration;
            } catch (Exception e) {
                Log.e(TAG, "Exception creating OutputConfiguration from parcel", e);
                return null;
            }
        }

        @Override
        public OutputConfiguration[] newArray(int size) {
            return new OutputConfiguration[size];
        }
    };

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        if (dest == null) {
            throw new IllegalArgumentException("dest must not be null");
        }
        dest.writeInt(mRotation);
        dest.writeInt(mSurfaceGroupId);
        dest.writeInt(mSurfaceType);
        dest.writeInt(mConfiguredSize.getWidth());
        dest.writeInt(mConfiguredSize.getHeight());
        dest.writeInt(mIsDeferredConfig ? 1 : 0);
        dest.writeInt(mIsShared ? 1 : 0);
        dest.writeTypedList(mSurfaces);
        dest.writeString(mPhysicalCameraId);
        dest.writeInt(mIsMultiResolution ? 1 : 0); // New in Android 12
        dest.writeList(mSensorPixelModesUsed); // New in Android 12
    }

    private ArrayList<Surface> mSurfaces;
    private final int mRotation;
    private final int mSurfaceGroupId;
    private final int mSurfaceType;
    private final Size mConfiguredSize;
    private final int mConfiguredFormat;
    private final int mConfiguredDataspace;
    private final int mConfiguredGenerationId;
    private final boolean mIsDeferredConfig;
    private boolean mIsShared;
    private String mPhysicalCameraId;
    private boolean mIsMultiResolution; // New in Android 12
    private ArrayList<Integer> mSensorPixelModesUsed; // New in Android 12
}

Quindi qual è il problema qui e perché questa modifica introduce una vulnerabilità? Come ho detto discutendo dell'esempio WindowContainerTransaction Parcelable, readList può effettivamente riempire la lista con qualsiasi oggetto supportato da Parcel, non solo quelli che corrispondono alla dichiarazione generica (ArrayList<Integer>). Tuttavia, poiché stiamo solo usando questa classe come parte di una catena di gadget di serializzazione e non la stiamo effettivamente usando, quel campo non verrà utilizzato per altro che leggere e scrivere su Parcel (e i tentativi di usare elementi da ArrayList contenenti elementi che non corrispondono alla sua dichiarazione generica porterebbero comunque solo a ClassCastException), questo non è di per sé un problema

In questa classe c'è anche un try-catch all'interno di createFromParcel, il che significa che se durante la lettura viene lanciata un'Exception, la lettura di OutputConfiguration verrà interrotta e la lettura dell'oggetto che contiene OutputConfiguration proseguirà. Quando ciò accade, l'intero OutputConfiguration verrà scritto su Parcel ma verrà letto solo fino al punto in cui si è verificata l'Exception. Questo crea una discrepanza poiché i dati non consumati scritti all'interno di OutputConfiguration.writeToParcel verranno effettivamente letti dall'oggetto che stava chiamando OutputConfiguration.CREATOR.createFromParcel

Ora, la combinazione di questi due elementi (permettere di annidare oggetti arbitrari supportati da Parcel e avvolgerli in un try-catch senza rilancio) dà la capacità di costruire un Parcelable che può essere scritto da system_server e successivamente letto in un modo controllato dall'app che ha inizialmente costruito il Parcelable inoltrato da system_server

Ok, quindi per costruire un tale Parcelable ora dobbiamo trovare qualcosa da mettere in mSensorPixelModesUsed che verrà letto con successo in system_server (poiché questo oggetto viene ricevuto dall'app attaccante tramite Parcel), scritto con successo da system_server e poi fallirà nell'unparcel e lancerà un'Exception nell'app vittima

Uno dei modi per farlo è usare una classe presente all'interno di system_server ma non nelle app, così che tentare di deserializzarla porti a ClassNotFoundException. Non posso però scegliere un Parcelable da system_server come readParcelable senza un ClassLoader esplicitamente specificato, poiché cercherà solo in BOOTCLASSPATH che non conterrà classi specifiche di system_server. La soluzione a questo problema è usare una delle classi Serializable, poiché ObjectInputStream prenderà il ClassLoader dal primo metodo non-BOOTCLASSPATH nello stack trace

Ho scelto PackageManagerException, tuttavia prima di usarla c'è un'altra cosa che dobbiamo fare. Nel costruttore di OutputConfiguration quando viene chiamato readList, l'argomento loader è esplicitamente impostato a Integer.class.getClassLoader(). Quel valore loader viene propagato a readValue(), poi a readSerializable() e all'interno di readSerializable() se il parametro loader non è null viene usato al posto di resolveClass da (quel controllo non fa nulla perché quando non trova la classe lancerà un'eccezione invece di restituire null). Il modo per aggirarlo è abbastanza semplice però, dobbiamo solo avvolgere in qualche che fa senza specificare . È qui che entra in gioco la classe descritta sopra

Quindi, a questo punto abbiamo il seguente oggetto:

  • OutputConfiguration
    • mSensorPixelModesUsed.get(0) = WindowContainerTransaction
      • mHierarchyOps.get(0) = PackageManagerException

Ora tale oggetto può essere deserializzato con successo all'interno di system_server: quando WindowContainerTransaction chiama readList proverà a trovare la classe PackageManagerException usando il ClassLoader di system server (non BootClassLoader), poiché può trovarlo nello stack trace. Quel class loader risulta essere presente nello stack trace perché mentre tutti i seguenti metodi non provenivano dal class path di system server: Binder#execTransact(), IActivityManager$Stub#onTransact() generato da AIDL e i metodi di tutte le classi Parcelable usate, c'era un metodo dichiarato all'interno di system server nello stack trace: un onTransact sovrascritto all'interno di ActivityManagerService. Pertanto può leggere e successivamente scrivere tale oggetto su e quando l'app di destinazione tenta di leggerlo, la classe non sarà disponibile e quindi verrà lanciata , e poi

Quindi abbiamo innescato questa discrepanza. Beh, non proprio ancora a questo punto perché l'eccezione è stata catturata quando non c'erano dati non letti rimasti da OutputConfiguration.writeToParcel, ma possiamo facilmente aggiungere un altro elemento alla List mSensorPixelModesUsed e quell'elemento verrà scritto tramite Parcel.writeValue e lasciato non letto dopo la lettura di OutputConfiguration

Metterlo in Intent

Come notato sopra, vorrò innescare la discrepanza dall'oggetto Intent, poiché verrà passato da system_server a un metodo AIDL che ha Intent nel primo parametro e informazioni di esecuzione nel secondo parametro, così che la serializzazione/deserializzazione dell'Intent passato nel primo parametro porti alla modifica del valore nel secondo parametro

In Intent.readFromParcel() tutti i valori vengono letti tramite metodi tipizzati dedicati, quindi lì non possiamo specificare una classe Parcelable personalizzata

All'interno di Intent però, c'è ClipData annidato e da Android 12 in ClipData$Item c'è un nuovo campo ActivityInfo mActivityInfo (non presente in AOSP al momento della scrittura iniziale, ecco il commit che introduce quel campo, questo campo viene letto tramite in.readTypedObject(ActivityInfo.CREATOR) all'interno del costruttore ClipData(Parcel in))

Poi all'interno del costruttore ActivityInfo(Parcel source) di nuovo non c'è modo di mettere un Parcelable personalizzato, ma poiché ActivityInfo estende ComponentInfo ha il campo applicationInfo

Infine in ApplicationInfo c'è il campo SparseArray<int[]> splitDependencies, che viene letto tramite readSparseArray, che a sua volta usa readValue per leggere gli elementi di SparseArray

A questo punto potremmo posizionare OutputConfiguration all'interno di splitDependencies, tuttavia la lettura di splitDependencies è seguita da alcune chiamate readString8() e sarebbe bello avere il controllo completo sui dati non consumati dopo che si verifica la discrepanza, così possiamo posizionare direttamente stringhe vuote lì e non preoccuparci di diverse interpretazioni dei dati non consumati

Per fare ciò, prima dobbiamo mettere un contenitore di dati grezzi all'interno di OutputConfiguration.mSensorPixelModesUsed che verrà scritto tramite writeValue. Ho scelto Bundle. In questo modo nei dati non consumati avremo lasciato:

  1. Tag VAL_BUNDLE di writeValue
  2. Lunghezza dei dati grezzi (questo link si applica anche agli elementi rimanenti in questa lista)
  3. BUNDLE_MAGIC
  4. Dati grezzi passati verbatim tramite Parcel.appendFrom

Quindi abbiamo tre elementi Parcel.writeInt che non avremmo consumato, possiamo sbarazzarcene avvolgendo OutputConfiguration in qualche Parcelable che durante la lettura legge un valore Parcelable arbitrario seguito da tre int. L'ho trovato nel CREATOR di ZenPolicy

Per riassumere abbiamo la seguente gerarchia di oggetti (che è presente in system_server e che tenta di passare a scheduleReceiver)

  • Intent
    • mClipData = ClipData
      • mItems.get(0).mActivityInfo = ActivityInfo
        • applicationInfo = ApplicationInfo
          • splitDependencies.get(0) = ZenPolicy
            • mVisualEffects.get(0) = OutputConfiguration
              • mSensorPixelModesUsed.get(0) = WindowContainerTransaction
                • mHierarchyOps.get(0) = PackageManagerException
              • mSensorPixelModesUsed.get(1) = Bundle

Questo viene scritto da system_server. Poi l'applicazione ricevente legge tutto fino a (inclusi i dati readSerializable di) PackageManagerException normalmente, tuttavia dopo che i dati Serializable per PackageManagerException vengono letti, viene lanciata un'eccezione e la lettura di tutto ciò che è sotto OutputConfiguration viene annullata, lasciando Bundle non letto. La lettura procede a ZenPolicy che consuma i tre int che precedono i dati grezzi all'interno di Bundle. Poi la lettura di ApplicationInfo procede con la lettura dei dati che erano precedentemente dati grezzi passati verbatim in Bundle. La lettura di quei dati grezzi continuerà con gli oggetti rimanenti in questo stack (ApplicationInfo, ActivityInfo, e ) e poi quei dati grezzi verranno usati per leggere il successivo parametro del metodo

Cosa succede poi all'interno di handleReceiver

Come ho appena detto, ora i parametri rimanenti di scheduleReceiver vengono letti dal buffer controllato dall'attaccante.

Diamo un'occhiata a cosa succede una volta che quel metodo viene invocato.

Prima scheduleReceiver impacchetta i valori da tutti gli argomenti e usa sendMessage() per passare l'esecuzione al thread principale

Poi, sul thread principale viene chiamato handleReceiver

handleReceiver chiama getPackageInfoNoCheck, passandogli ApplicationInfo che ha ricevuto come parte di ActivityInfo che è stato passato all'argomento di scheduleReceiver

getPackageInfo controlla se il pacchetto con il nome dato è già presente nella cache e se non lo è costruisce una nuova istanza LoadedApk, passandogli l'oggetto ApplicationInfo ricevuto in precedenza (Poiché l'attaccante vuole causare la costruzione di un nuovo LoadedApk, viene usato un packageName di un pacchetto che non era stato visto in precedenza in questo processo)

Poi viene usato il metodo ContextImpl.getClassLoader(), che al primo avvio delega a mPackageInfo.getClassLoader(), con mPackageInfo che è un LoadedApk costruito nel paragrafo precedente

Poi c'è createOrUpdateClassLoaderLocked, che chiama makePaths per popolare zipPaths con i percorsi da usare nel ClassLoader, poi vengono uniti e assegnati alla variabile zip e questo viene passato a createClassLoader

makePaths riempie zipPaths usando le informazioni da ApplicationInfo, e soprattutto questo include sourceDir. L'applicazione attaccante crea un ApplicationInfo iniettato con sourceDir impostato sul percorso del proprio apk, quindi la classe receiver verrà effettivamente caricata dall'apk dell'attaccante. Questo porta direttamente all'esecuzione di codice controllato dall'attaccante all'interno dell'applicazione che riceve la broadcast

Nota sui controlli delle API nascoste

C'era un'altra cosa che doveva essere aggirata: i controlli delle API nascoste. Questi non sono mai stati pensati come confine di sicurezza (poiché un'applicazione può sempre usare NDK e chiamare direttamente le syscall sottostanti), ma in questo caso sono stati aggirati costruendo un ClipData artefatto scrivendo manualmente i dati su Parcel e poi usando readParcelable. Tale ClipData poteva poi essere normalmente allegato a un Intent e poi passato a sendBroadcast(), quindi l'invio della broadcast stessa è stato fatto usando solo API pubbliche

Correzioni

Il writeup sopra è stato originariamente inviato a Google e sembra che ne abbiano fatto uso poiché ci sono molteplici correzioni che ne derivano (penso, non ho prove sulla causalità diretta)

Rilasciate con Android 12:

  • La soppressione delle eccezioni da OutputConfiguration e classi correlate è stata rimossa
  • OutputConfiguration#mSensorPixelModesUsed non viene più scritto tramite writeValue
  • ClipData#mActivityInfo non viene più scritto su Parcel a meno che non sia esplicitamente richiesto durante la scrittura (quindi Intent non può più contenere Parcelable arbitrari da BOOTCLASSPATH, eliminando questa tecnica di sfruttamento)

Presenti solo sul ramo master al momento della scrittura, non nelle versioni rilasciate, probabilmente appariranno in Android 13 (non in 12L):

  • Ci sono nuovi metodi di lettura List su Parcel che controllano il tipo degli elementi e le versioni senza tipo sono state marcate come deprecate
  • C'è un nuovo metodo Parcel#enforceNoDataAvail() che controlla che non ci siano dati non letti rimasti in Parcel, che apparentemente verrà usato da AIDL dopo la lettura degli argomenti delle chiamate RPC. Di solito i miei exploit si basavano sul fatto che dopo che tutti i dati erano stati letti da Parcel tutto il resto veniva ignorato, questo non sarà più il caso, anche se penso che in molti casi si potrebbero costruire dati che causeranno alla fine un seek alla posizione finale, quindi questa non è una mitigazione forte. Comunque a volte tali problemi si verificano naturalmente senza essere rilevati, quindi questo li catturerebbe. Ulteriore discussione su questo è nell'issue #3
  • Ogni elemento in Bundle avrà la sua lunghezza salvata separatamente. Questo uccide praticamente l'intera classe di bug di cui ho segnalato bug privatamente a Google dal 2014, descrizione pubblicata nel 2017 e codice stesso circa un anno dopo. Se conto correttamente, saranno 8 anni di vita della classe di bug (onestamente non ho idea se sia tanto, anche se potrebbero esserci ancora varianti di exploit non-Bundle, come esattamente questa (anche se esattamente questa è già stata corretta))
Scarica lo strumento
ObjectInputStream
c != null
Class.forName
PackageManagerException
Parcelable
readList
ClassLoader
WindowContainerTransaction
system_server
Parcel
PackageManagerException
ClassNotFoundException
avvolta in RuntimeException
catturata dal CREATOR di OutputConfiguration
ClipData
Intent
handleReceiver