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
TheLastBundleMismatch — Writeup ed exploit per CVE-2023-45777, bypass per la validazione degli Intent all'interno di AccountManagerService su Android 13 nonostante la mitigazione "Lazy Bundle" | Kitploit
Strumenti/GitHubGitHub/michalbednarski/thelastbundlemismatch
Sicurezza AndroidAnalisi delle VulnerabilitàExploitAnalisi di BinariPaper e Ricerca
GitHubmichalbednarski/thelastbundlemismatch

TheLastBundleMismatch

Writeup ed exploit per CVE-2023-45777, bypass per la validazione degli Intent all'interno di AccountManagerService su Android 13 nonostante la mitigazione "Lazy Bundle"

Vedi Repository

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
101142 anni faRevisionato da Kitploit

Patch misterioso

Iniziamo questa volta con la patch apparsa come fix per CVE-2023-45777 nel Android Security Bulletin:```diff diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 7a19d034c2c8..5238595fe2a2 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4923,7 +4923,7 @@ public class AccountManagerService p.setDataPosition(0); Bundle simulateBundle = p.readBundle(); p.recycle();

  • root@kitploit:~
           Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
    
  • root@kitploit:~
           Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
           if (intent != null && intent.getClass() != Intent.class) {
               return false;
           }
    
root@kitploit:~
Poche persone erano abbastanza perplesse da chiedermelo; in precedenza ho risposto loro con alcuni suggerimenti e ora pubblico il writeup completo per questo problema

Ma prima forniamo un po' di contesto su cosa sta succedendo in questa patch

Questo è un cambiamento nel metodo [`checkKeyIntent()`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=4938-4954;drc=47de64a38aa1799cb41f41b2ea0c539ee61de64d). Questo metodo esegue più controlli per garantire che l'`Intent` fornito dall'applicazione sia sicuro per il sistema da lanciare (usando i privilegi del sistema)

Innanzitutto, questo metodo usa `checkKeyIntentParceledCorrectly()` che serializza e deserializza nuovamente il `Bundle` che stiamo controllando e verifica se l'`Intent` preso dal `Bundle` prima di ciò corrisponde all'`Intent` dal `Bundle` dopo tale ciclo. Poiché il lancio dell'`Intent` avviene in altri processi di app di sistema diversi da quello che esegue la validazione, in precedenza era [possibile costruire `Bundle` che apparivano sicuri durante la validazione all'interno di `AccountManagerService`, ma contenevano un `Intent` diverso dopo essere stati inviati al processo successivo](https://github.com/michalbednarski/IntentsLab/issues/2#issuecomment-344365482). Questo simula l'invio del `Bundle` al processo successivo per rilevare tali situazioni.

Dopo `checkKeyIntentParceledCorrectly()` abbiamo la chiamata `bundle.getParcelable()`, che questa patch sostituisce dalla versione deprecata che poteva costruire qualsiasi oggetto a una che valida che l'oggetto che sta per essere deserializzato sia del tipo specificato nel secondo parametro

Quella versione con il parametro di tipo è stata introdotta in Android 13, come parte di un più ampio rafforzamento di `Parcel`/`Bundle`. In particolare, prima di Android 13, quando un `Bundle` veniva inviato tra processi, manteneva una copia grezza dell'intero dato serializzato finché non veniva acceduto a qualsiasi elemento, momento in cui ogni valore veniva deserializzato. Ora, quando un valore viene acceduto per la prima volta dopo che il `Bundle` è stato ricevuto, vengono deserializzate solo le chiavi `String` e i valori dei tipi primitivi, mentre i valori non primitivi vengono lasciati come `LazyValue`, che hanno la loro lunghezza memorizzata come parte del dato serializzato per garantire che anche quando la logica di serializzazione/deserializzazione non corrisponde, tali discrepanze non influenzino altre voci

Prima di addentrarci, diamo un'occhiata a `LazyValue`: nel suo codice sorgente [abbiamo un bel commento che spiega la sua struttura dati](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=4392-4399;drc=03c34f57c05feecfb090de3917787f049cb5f804)```
                     |   4B   |   4B   |
mSource = Parcel{... |  type  | length | object | ...}
                     a        b        c        d
length = d - c
mPosition = a
mLength = d - a

mPosition e mLength descrivono la posizione dell'intero dato LazyValue nel Parcel originale, inclusi type e length. "length" (senza "m" all'inizio) si riferisce al valore di lunghezza come scritto nel Parcel ed esclude l'header (type e length)

Se un Bundle contenente un LazyValue viene inoltrato a un altro processo, l'intero LazyValue inclusi i campi type e length viene copiato verbatim da Bundle.mParcelledData al Parcel di destinazione

Quando si accede all'elemento del Bundle rappresentato da LazyValue, il Parcel viene riavvolto a mPosition e viene chiamato readValue(). Se viene passato un argomento di tipo a bundle.getParcelable(), questo viene propagato a readValue() che garantirà sia che il tipo in procinto di essere de-parcellizzato sia quello atteso, sia che dopo la de-parcellizzazione venga verificato che il tipo del valore de-parcellizzato sia quello atteso. Dopo la de-parcellizzazione, LazyValue viene sostituito, così la volta successiva che il Bundle viene scritto nel Parcel, il valore verrà serializzato di nuovo tramite writeValue()

L'uso del parametro tipizzato Bundle.get*()/Parcel.read*() è per lo più rilevante per metodi come Parcel.readParcelableList(), che restituisce un ArrayList e, a causa della Type Erasure di Java, anche se facessi qualcosa come List<SomeParcelableType> field = parcel.readParcelableList();, la parte <SomeParcelableType> non verrebbe applicata a runtime e tale List potrebbe contenere qualsiasi classe Parcelable disponibile nel sistema, quindi tutti i createFromParcel/writeToParcel disponibili nel sistema potrebbero essere usati come parte della serializzazione/deserializzazione del tipo che conteneva tale List

Potresti anche voler dare un'occhiata alla presentazione del team Android Security and Privacy sull'introduzione di questi meccanismi (slide, video)

Qui, tuttavia, l'uso della versione tipizzata sembra ridondante, poiché controlliamo anche esplicitamente il tipo dell'oggetto restituito. Allora cosa sta succedendo e quale vulnerabilità viene corretta qui?

Effetti collaterali

Riguarda la patch dall'inizio

  • Se il valore deserializzato sotto la chiave "intent" è un Intent
    • Verrà validato per puntare al componente che è sicuro per il sistema avviare
    • Se un mismatch potesse essere innescato dall'oggetto Intent, avremmo un problema molto più grande
  • Se il valore deserializzato non è un Intent
    • Per fare qualcosa di dannoso, dovremmo avere un Intent dentro il Bundle dopo che viene inviato a un altro processo, ma il tipo del Parcelable viene salvato a un offset precedente rispetto a qualsiasi possibile mismatch e il prefisso di lunghezza di LazyValue ci impedisce di modificare le successive coppie chiave-valore in caso di mismatch writeToParcel/createFromParcel

Quindi, cosa potrebbe fare di pericoloso qui la chiamata a bundle.getParcelable(AccountManager.KEY_INTENT) senza argomento di tipo?

[Risposta nel prossimo paragrafo, prova a indovinare prima di continuare a leggere. Se avessi una fursona, questo sarebbe il posto per un po' di arte]

La risposta è chiamare un createFromParcel() non correlato che in realtà modifica i dati grezzi del LazyValue che è memorizzato sotto una chiave diversa e verrà passato verbatim al processo successivo

Abbiamo un'implementazione di createFromParcel() che può effettivamente chiamare writeInt() sul Parcel fornito

Ma non perché writeInt sia stato posizionato per errore, ma a causa della riflessione senza restrizioni. In particolare, all'interno di PackageParser abbiamo il seguente codice:```java final Class cls = (Class) Class.forName(componentName); final Constructor cons = cls.getConstructor(Parcel.class);

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

root@kitploit:~
We can have `Parcel` object which was passed to `createFromParcel` passed to any available in system `public` constructor that accepts single `Parcel` argument

And then [we have following code](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/os/PooledStringWriter.java;l=51-56;drc=782d49826862cbdc9d020fc9d85f8a6f64675dcb):```java
public PooledStringWriter(Parcel out) {
    mOut = out;
    mPool = new HashMap<>();
    mStart = out.dataPosition();
    out.writeInt(0); // reserve space for final pool size.
}

Abbiamo un costruttore che chiama writeInt(0) sul Parcel fornito, tuttavia ci sono alcune cose che complicano lo sfruttamento

Prima di tutto, anche se non è direttamente visibile nel codice sorgente, subito dopo che newInstance() viene chiamato, viene eseguito un cast e viene lanciata una ClassCastException

Ingoiare l'eccezione

Mi serviva qualcosa che durante createFromParcel chiamasse createFromParcel di un'altra classe all'interno di un blocco try e poi fallisse nel propagare l'eccezione catturata

Questa è la parte in cui l'exploit in realtà non funziona su AOSP puro, ho usato una classe specifica di Samsung

Ho incluso una copia delle parti rilevanti di quella classe in questo repository

Questo repository include anche uno script che la integra in AOSP, quindi per i test puoi eseguirlo (passa il percorso del tuo checkout AOSP come argomento, es. ./make-aosp-buggy.sh /path/to/aosp), ripristina la modifica descritta all'inizio del writeup ed esegui questo exploit contro la tua build AOSP

In precedenza ho usato la classe OutputConfiguration di AOSP per ingoiare le eccezioni, prima di Android 13 ingoiare un'eccezione in createFromParcel() combinato con la possibilità di costruire altri oggetti Parcelable è di per sé una vulnerabilità, tuttavia nel caso di SemImageClipData l'inghiottimento delle eccezioni non era presente su queste versioni di Android

C'è però un'importante differenza tra SemImageClipData e il OutputConfiguration usato in precedenza: anche se SemImageClipData cattura un'eccezione, restituisce comunque un oggetto non nullo e se in seguito verrà castato a un altro tipo, ciò innescherebbe una ClassCastException, che è esattamente ciò che stiamo cercando di evitare

La cancellazione del tipo in Java colpisce ancora

La cancellazione del tipo in Java significa che i metodi generici in realtà non conoscono il tipo generico usato dal chiamante. Questo di solito aiutava lo sfruttamento```java // When we read some List, this actually didn't check if list contains only SomeParcelableType List myList = sourceParcel.readParcelableList();

// Above is why Android 13 has introduced typed methods that enforce type at runtime List myList = sourceParcel.readParcelableList(SomeParcelableType.class);

// If untyped method was used when reading, list can contain non-SomeParcelableType // items and they would be written without errors targetParcel.writeParcelableList(myList, 0);

// However if List contains non-SomeParcelableType item, this would throw during item access // (That however commonly didn't happen if we used Parcelable object only as container in gadget chain) SomeParcelableType myItem = myList.get(0);

root@kitploit:~
Questa volta l'eliminazione del tipo non ha giocato a nostro favore. Prima avevamo un metodo che invocava effettivamente il costruttore tramite reflection```java
private static <T extends IntentInfo> ArrayList<T> createIntentsList(Parcel in) {
    // ...
    final ArrayList<T> intentsList;
    // ...
    intentsList.add(cons.newInstance(in));
    // ...
    return intentsList;
}

Questo metodo ha un parametro generico T. Non importa quale tipo di parametro sia stato utilizzato dal chiamante, tuttavia, poiché nella dichiarazione di questo metodo c'è <T extends IntentInfo>, la riga con la chiamata a newInstance() diventa intentsList.add((IntentInfo) cons.newInstance(in));, anche se newInstance() restituisce Object e ArrayList.add() accetta Object come argomento. Questo ha introdotto la necessità di avvolgere la chiamata con un qualche Parcelable che assorba l'Exception

Poi abbiamo la chiamata a `bundle.getParcelable()````java @Deprecated @Nullable public T getParcelable(@Nullable String key) { unparcel(); Object o = getValue(key); if (o == null) { return null; } try { return (T) o; } catch (ClassCastException e) { typeWarning(key, o, "Parcelable", e); return null; } }

root@kitploit:~
La procedura di deserializzazione viene eseguita tramite la chiamata `getValue()`, che in realtà porta alla chiamata `createFromParcel()`. Se lì si verifica una `ClassCastException`, non verrà catturata. `getValue()` ora restituisce qualunque valore sia stato deserializzato per questa chiave tramite [`parcel.readValue()`](https://developer.android.com/reference/android/os/Parcel#readValue(java.lang.ClassLoader))

Tuttavia, se inseriamo `SemImageClipData` come valore, all'interno del blocco `try`-`catch` proveremmo a fare il cast a `T`, che in questo caso è `Parcelable` come dichiarato nella dichiarazione generica del metodo. Il chiamante usa questo metodo come generico con `T` che è un `Intent`, ma `getParcelable()` non lo sa e il cast a `Intent` avviene nel chiamante, quindi la `ClassCastException` viene lanciata fuori dal `try`

Possiamo però avvolgere il nostro `SemImageClipData` all'interno di un array `Parcelable[]`, quindi il cast a `T` all'interno di `getParcelable()` fallirà nel convertire `Parcelable[]` in `Parcelable` e lancerà una `ClassCastException` all'interno del `try`; quell'`Exception` verrà registrata nei log e verrà restituito `null`, che poi verrà accettato da `checkKeyIntent()`

# Il Layout

Quindi ora dobbiamo allineare il contenuto all'interno del `Bundle` così che dopo il ciclo `writeToParcel`/`createFromParcel` il suo contenuto sia quello che abbiamo preparato

Ma a differenza del tipico "`Bundle` FengShui" in cui il trigger è avere `createFromParcel` che legge più o meno dati di quanti `writeToParcel` ne avesse scritti in precedenza, qui abbiamo `writeInt(0)` che sovrascrive parte del `LazyValue` non deserializzato

Ecco quindi come appare `Bundle.mParcelledData` quando viene inizialmente deparcellato da `AccountManagerService` (Offset ottenuti chiamando `dataPosition()` tramite debugger collegato a `system_server`)

<table>
<tr><th>Offset</th><th>Valore</th><th>Nota</th></tr>
<tr><td>0</td><td>3</td><td>Numero di coppie chiave-valore</td></tr>
<tr><td>4</td><td>"intent"</td><td>Prima chiave nel <code>Bundle</code>, quella a cui si accederà tramite <code>getParcelable(AccountManager.KEY_INTENT)</code></td></tr>
<tr><td>24</td><td>16</td><td>Il primo <code>LazyValue</code> inizia qui, il tipo è <code>VAL_PARCELABLEARRAY</code></td></tr>
<tr><td>28</td><td>340</td><td>Lunghezza dichiarata del <code>LazyValue</code>, usata per trovare la chiave successiva nel <code>Bundle</code>. Il nostro <code>LazyValue</code> in realtà non avrà questa dimensione dopo essere stato letto, ma <code>LazyValue.apply</code> <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/os/Parcel.java;l=4501-4505;drc=4d6b008243a5b1b1fb4e725e37e14651a24a4a4d">lo segnala tramite <code>Slog.wtfStack()</code></a> che <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Slog.java;l=230-235;drc=4d6b008243a5b1b1fb4e725e37e14651a24a4a4d">non lancia eccezioni</a></td></tr>
<tr><td>32</td><td>1</td><td>Lunghezza dell'array <code>Parcelable[]</code>; l'array ha un solo elemento ed è presente così che la <code>ClassCastException</code> avvenga all'interno del blocco <code>try</code> che si trova in <code>bundle.getParcelable()</code></td></tr>
<tr><td>36</td><td>"com.samsung.android.<br>content.clipboard.data.<br>SemImageClipData"</td><td>Nome della classe <code>Parcelable</code>, questa è la classe wrapper che assorbirà l'Exception</td></tr>
<tr><td>160</td><td>2</td><td>Tag di tipo usato da <code>createClipBoardData()</code></td></tr>
<tr><td>164</td><td></td><td>Elementi letti dal costruttore della superclasse di <code>SemImageClipData</code> (che includono la chiamata <code>readParcelable()</code>, che però avviene fuori dal blocco <code>try</code>). Non particolarmente rilevanti, ma dobbiamo attraversarli prima di raggiungere la parte interessante di <code>createFromParcel()</code></td></tr>
<tr><td>252</td><td></td><td>Dati letti da <code>SemImageClipData.readFromSource()</code></td></tr>
<tr><td>272</td><td>"android.content.pm.<br>PackageParser&#36;Activity"</td><td>Nome del <code>Parcelable</code> letto da <code>mExtraParcelFd = in.readParcelable()</code>. Il tipo non corrisponde, tuttavia prima del cast verrà comunque lanciata un'Exception</td></tr>
<tr><td>360</td><td></td><td>Campi <code>className</code> &amp; <code>metadata</code> di <code>PackageParser$Component</code></td></tr>
<tr><td>368</td><td>1</td><td>Numero di elementi in <code>createIntentsList()</code></td></tr>
<tr><td>372</td><td>"android.os.<br>PooledStringWriter"</td><td>Nome della classe che verrà istanziata tramite <code>Class.forName().getConstructor(Parcel.class).newInstance()</code>. In questa posizione termina il primo <code>LazyValue</code>, ma il suo parsing continua perché <code>readValue()</code> non ha raggiunto la fine. Viene anche interpretato come seconda chiave nel <code>Bundle</code> durante l'<code>unparcel()</code> iniziale</td></tr>
<tr><td>436</td><td>4</td><td>Il secondo <code>LazyValue</code> inizia qui; questo 4 è <code>VAL_PARCELABLE</code> per cui <code>Parcel.isLengthPrefixed()</code> restituirà <code>true</code>. Questo valore verrà successivamente sovrascritto dal costruttore di <code>PooledStringWriter</code>, dopo di che verrà lanciata un'Exception e <code>getParcelable(AccountManager.KEY_INTENT)</code> terminerà</td></tr>
<tr><td>440</td><td>240</td><td>Lunghezza del <code>LazyValue</code> il cui tipo era dichiarato come <code>VAL_PARCELABLE</code>; viene usata per determinare la posizione della voce successiva e quanti dati devono essere copiati nel <code>Bundle</code> di destinazione durante la ri-serializzazione. Questo <code>LazyValue</code> non viene effettivamente deparcellato e viene usato come contenitore di dati grezzi</td></tr>
<tr><td>684</td><td>"1&y~pw"</td><td rowspan="2">Terza coppia chiave-valore; la chiave è generata casualmente per avere un <code>hashCode()</code> Java superiore a quelli usati in precedenza (gli elementi memorizzati in <code>ArrayMap</code> sono ordinati per <code>hashCode()</code> crescente della chiave e questo è l'ordine in cui gli elementi del <code>Bundle</code> verranno scritti nel <code>Parcel</code>). Questa coppia chiave-valore è presente qui solo per aumentare il numero totale di coppie scritte, poiché quello sarà il numero di coppie lette, anche se questa coppia in realtà non verrà letta</td></tr>
<tr><td>704</td><td>-1 (<code>VAL_NULL</code>)</td></tr>
</table>

Poi, quando il Bundle viene serializzato di nuovo, appare così:

<table>
<tr><th>Offset</th><th>Valore</th><th>Nota</th></tr>
<tr><td>0</td><td>3</td><td>Numero di coppie chiave-valore</td></tr>
<tr><td>4</td><td>"intent"</td><td>Prima chiave nel <code>Bundle</code></td></tr>
<tr><td>24</td><td>16</td><td><code>VAL_PARCELABLEARRAY</code>; l'array <code>Parcelable[]</code> precedentemente deserializzato viene ora serializzato di nuovo</td></tr>
<tr><td>28</td><td>196</td><td>Lunghezza del <code>LazyValue</code>, cioè il nostro oggetto <code>SemImageClipData</code> avvolto. Questa lunghezza è presa dall'esecuzione con il mio <code>SemImageClipData</code> fittizio e quindi gli offset presentati da questo punto in poi non corrisponderanno a quelli che apparirebbero su un dispositivo Samsung reale; tuttavia questo <code>LazyValue</code> non verrà deserializzato di nuovo, quindi non importa per l'esecuzione dell'exploit</td></tr>
<tr><td>224</td><td>"android.os.<br>PooledStringWriter"</td><td>Seconda chiave nel <code>Bundle</code></td></tr>
<tr><td>288</td><td>0</td><td>Il secondo <code>LazyValue</code> inizia qui; l'elemento sotto la chiave <code>"android.os.PooledStringWriter"</code> non è stato acceduto, quindi questo <code>LazyValue</code> viene copiato dai dati originali, tuttavia il tag di tipo è stato sovrascritto dalla chiamata <code>writeInt(0)</code> eseguita dal costruttore di <code>PooledStringWriter</code> e al raggiungimento del processo di destinazione non viene più interpretato come un <code>LazyValue</code></td></tr>
<tr><td>292</td><td>240</td><td>Questa era la lunghezza del secondo <code>LazyValue</code> copiato dal <code>Bundle</code> originale, ma poiché il tag di tipo è stato sovrascritto con <code>writeInt(0)</code>, che è <code>VAL_STRING</code>, questo valore viene ora letto tramite <code>readString()</code>. In precedenza, per <code>LazyValue</code>, la lunghezza era espressa in byte, ma ora, per <code>String</code>, è espressa in caratteri a due byte. Non ci sono abbastanza dati nel <code>Parcel</code> sorgente per questo, quindi <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/binder/Parcel.cpp;l=2221-2226;drc=4d6b008243a5b1b1fb4e725e37e14651a24a4a4d">il <code>parcel->readString16Inplace()</code> nativo fallisce dopo aver letto la lunghezza</a>, ma ciò non causa un'Exception sul lato Java</td></tr>
<tr><td>296</td><td>"intent"</td><td>"Terza" chiave nel <code>Bundle</code>. In realtà sovrascrive la prima chiave: poiché <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/ArrayMap.java;l=651-659;drc=584140c83a456b5de99880b440c2d5dfc3c70506">"intent" ha un <code>hashCode()</code> minore rispetto alla chiave vista in precedenza, il metodo <code>ArrayMap.append()</code> usa <code>put()</code> che consente di sostituire i valori</a>, altrimenti <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/ArrayMap.java;l=667-675;drc=584140c83a456b5de99880b440c2d5dfc3c70506">avremmo una chiave duplicata che verrebbe successivamente rifiutata da <code>validate()</code></a></td></tr>
<tr><td>316</td><td>4</td><td><code>VAL_PARCELABLE</code>; qui inizia il <code>LazyValue</code> contenente l'<code>Intent</code> effettivo che verrà avviato</td></tr>
<tr><td>536</td><td>"1&y~pw"</td><td>Elemento di riempimento che viene scritto ma non letto perché tutte e 3 le coppie chiave-valore sono già state lette. <a href="https://cs.android.com/android/_/android/platform/system/tools/aidl/+/96a02f50fdfa4d20aa46ae2dde927257eac46d4a">A differenza delle interfacce AIDL</a>, non viene eseguito alcun controllo <code>enforceNoDataAvail()</code> sul <code>Bundle</code> (ma anche se ci fosse, <a href="https://github.com/michalbednarski/ReparcelBug2/issues/3">potrebbe essere aggirato inserendo una voce fittizia che specifica la lunghezza prevista</a>)</td></tr>
</table>

# Come è successo due volte

Discutiamo ora quattro patch, due delle quali correggono la vulnerabilità oggetto di questo writeup

* CVE-2023-20944 ([bollettino](https://source.android.com/docs/security/bulletin/2023-02-01#framework), [patch](https://android.googlesource.com/platform/frameworks/base/+/d0bc9026e2e62e09fa88c1bcbf1dc1c3fb001375%5E%21/)): Questa è un'altra vulnerabilità trovata da me. Analogamente a questa, la patch non rende ovvio come potrebbe essere sfruttata, ma [sembra che qualcun altro l'abbia capito (post del blog in cinese)](https://konata.github.io/posts/creator-mismatch/)
* CVE-2023-21098 ([bollettino](https://source.android.com/docs/security/bulletin/2023-04-01#framework), [patch](https://android.googlesource.com/platform/frameworks/base/+/107e6377328486fca55131ea06ca9d6a3c1585e0%5E%21/)): Questa è la prima volta che ho segnalato l'exploit presentato qui. Quella patch introduce anche una correzione per il bypass di `checkKeyIntentParceledCorrectly()` applicabile alle versioni di Android precedenti alla 13
* CVE-2023-35669 ([bollettino](https://source.android.com/docs/security/bulletin/2023-09-01#framework), [patch](https://android.googlesource.com/platform/frameworks/base/+/f810d81839af38ee121c446105ca67cb12992fc6%5E%21/)): Questa non è in risposta alla mia segnalazione, ma penso sia stata fatta per correggere lo stesso problema di CVE-2023-20944, ma per i casi in cui `AccountManager.KEY_INTENT` viene avviato da Activity diverse da `ChooseTypeAndAccountActivity` (ad esempio [`AddAccountSettings`](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/accounts/AddAccountSettings.java;l=95-107;drc=32813a2bef49b172aed89122b4eb50bf14026ddc), che ho mancato quando ho segnalato il bug la prima volta). Questa modifica ha sostituito l'uso del tipizzato `bundle.getParcelable()` con l'uso di quello non tipizzato e il controllo manuale `getClass() != Intent.class`, che in realtà ha annullato la correzione per CVE-2023-21098
* CVE-2023-45777 ([bollettino](https://source.android.com/docs/security/bulletin/2023-12-01#framework), [patch](https://android.googlesource.com/platform/frameworks/base/+/f4644b55d36a549710ba35b6fb797ba744807da6%5E%21/)): Questa è la seconda volta che ho segnalato questo exploit. La patch ha mantenuto il controllo manuale `getClass() != Intent.class`, ma in aggiunta ha ripristinato l'uso del tipizzato `bundle.getParcelable()`, che è un buon modo per correggere entrambi i problemi

Sebbene lo stesso exploit funzioni sia per CVE-2023-21098 che per CVE-2023-45777, il modo in cui è riuscito a bypassare `checkKeyIntentParceledCorrectly()` differisce

Nel caso di CVE-2023-21098, [`checkKeyIntent()` non veniva effettivamente chiamato se il `Bundle` controllato non conteneva un `Intent`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=3519-3521;drc=cdd30b5c040ba7ebd0a1cc6009183ff602434fc0). Poiché `checkKeyIntent()` è ciò che chiama `checkKeyIntentParceledCorrectly()`, nel caso in cui il `Bundle` originale non sembrasse contenere un `Intent`, il `Bundle` dopo la ri-serializzazione non veniva controllato

Nel caso di CVE-2023-45777, `checkKeyIntentParceledCorrectly()` veniva chiamata correttamente, tuttavia [`writeBundle()` avveniva lì prima della chiamata `getParcelable()` senza argomento di tipo (fino alla quale il `Bundle` non cambiava il suo contenuto)](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=4921-4929;drc=b0f6558fb36eb76df35c516ec5a65030a34a8734)
Scarica lo strumento