Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
TheLastBundleMismatch — Writeup y exploit para CVE-2023-45777, bypass de la validación de Intent dentro de AccountManagerService en Android 13 a pesar de la mitigación "Lazy Bundle" | Kitploit
Herramientas/GitHubGitHub/michalbednarski/thelastbundlemismatch
Seguridad AndroidAnálisis de VulnerabilidadesExplotaciónAnálisis de BinariosPapers e Investigación
GitHubmichalbednarski/thelastbundlemismatch

TheLastBundleMismatch

Writeup y exploit para CVE-2023-45777, bypass de la validación de Intent dentro de AccountManagerService en Android 13 a pesar de la mitigación "Lazy Bundle"

Ver Repositorio

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
10114hace 2 añosRevisado por Kitploit

Parche misterioso

Empecemos esta vez con el parche que apareció como corrección para CVE-2023-45777 en el 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:~
Pocas personas sintieron suficiente curiosidad como para preguntarme; anteriormente les respondí con algunas pistas y ahora estoy publicando el análisis completo de este problema.

Pero primero, proporcionemos algo de contexto sobre qué está ocurriendo en este parche.

Este es un cambio en el método [`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). Este método realiza múltiples comprobaciones para garantizar que el `Intent` proporcionado por la aplicación sea seguro para que el sistema lo lance (usando los privilegios del sistema).

Primero, este método usa `checkKeyIntentParceledCorrectly()`, que serializa y deserializa nuevamente el `Bundle` que estamos comprobando y verifica si el `Intent` obtenido del `Bundle` antes de eso coincide con el `Intent` del `Bundle` después de dicho ciclo. Dado que el lanzamiento del `Intent` ocurre en otros procesos de aplicaciones del sistema distintos de aquel que realiza la validación, anteriormente era [posible construir `Bundle`-s que parecían seguros durante la validación dentro de `AccountManagerService`, pero que contenían un `Intent` diferente después de ser enviados al siguiente proceso](https://github.com/michalbednarski/IntentsLab/issues/2#issuecomment-344365482). Esto simula el envío del `Bundle` al siguiente proceso para detectar tales situaciones.

Después de `checkKeyIntentParceledCorrectly()` tenemos la llamada a `bundle.getParcelable()`, que este parche cambia de la versión obsoleta que podía construir cualquier objeto a una que valida que el objeto que está a punto de deserializarse sea del tipo especificado en el segundo parámetro.

Esa versión con parámetro de tipo se introdujo en Android 13, como parte de un endurecimiento más amplio de `Parcel`/`Bundle`. En particular, antes de Android 13, cuando un `Bundle` se enviaba entre procesos, mantenía una copia sin procesar de todos los datos serializados hasta que se accedía a cualquier elemento, momento en el que se deserializaba cada valor. Ahora, cuando se accede a cualquier valor por primera vez después de que el `Bundle` haya sido recibido, solo se deserializan las claves `String` y los valores de tipos primitivos, mientras que los valores no primitivos se dejan como `LazyValue`-s, que tienen su longitud almacenada como parte de los datos serializados para garantizar que, incluso cuando la lógica de serialización/deserialización no coincida, dichas discrepancias no afecten a otras entradas.

Antes de profundizar, echemos un vistazo a `LazyValue`: en su código fuente [tenemos un buen comentario que explica su estructura de datos](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 y mLength describen la ubicación de todos los datos de LazyValue en el Parcel original, incluyendo type y length. "length" (sin la "m" al principio) se refiere al valor de longitud tal como se escribe en el Parcel y excluye el encabezado (type y length)

Si el Bundle que contiene LazyValue se reenvía a otro proceso, todo el LazyValue incluyendo los campos type y length se copia textualmente de Bundle.mParcelledData al Parcel de destino

Cuando se accede al elemento del Bundle representado por LazyValue, el Parcel se rebobina hasta mPosition y se llama a readValue(). Si se pasa un argumento de tipo a bundle.getParcelable(), este se propaga a readValue(), que tanto garantizará que el tipo que se va a desparcelar sea el esperado como verificará después de desparcelar que el tipo del valor desparcelado sea el esperado. Después de desparcelar, LazyValue se reemplaza, de modo que la próxima vez que el Bundle se escriba en el Parcel, el valor se serializará de nuevo mediante writeValue()

El uso del parámetro tipado de Bundle.get*()/Parcel.read*() es relevante principalmente para métodos como Parcel.readParcelableList(), que devuelve un ArrayList y, debido al borrado de tipos de Java, incluso si hicieras algo como List<SomeParcelableType> field = parcel.readParcelableList();, la parte <SomeParcelableType> no se aplicaba en tiempo de ejecución y dicha List podría contener cualquier clase Parcelable disponible en el sistema y, por lo tanto, todos los createFromParcel/writeToParcel disponibles en el sistema podrían usarse como parte de la serialización/deserialización del tipo que contenía dicha List

También te puede interesar consultar la presentación del equipo de Seguridad y Privacidad de Android sobre la introducción de estos mecanismos (diapositivas, vídeo)

Aquí, sin embargo, el uso de la versión tipada parece redundante, ya que también comprobamos explícitamente el tipo del objeto devuelto. Entonces, ¿qué está pasando y qué vulnerabilidad se está corrigiendo aquí?

Efectos secundarios

Echa un vistazo al parche desde el principio de nuevo

  • Si el valor deserializado bajo la clave "intent" es un Intent
    • Se validará para que apunte al componente que es seguro que el sistema inicie
    • Si se pudiera provocar una discrepancia desde el objeto Intent, tendríamos un problema mucho mayor
  • Si el valor que se está deserializando no es un Intent
    • Para hacer algo malo, necesitaríamos tener un Intent dentro del Bundle después de que se envíe a otro proceso, pero el tipo de Parcelable se guarda en un desplazamiento anterior a cualquier posible discrepancia y el prefijo de longitud de LazyValue nos impide modificar los siguientes pares clave-valor en caso de discrepancia entre writeToParcel/createFromParcel

Entonces, ¿qué cosa peligrosa podría hacer aquí la llamada a bundle.getParcelable(AccountManager.KEY_INTENT) sin argumento de tipo?

[Respuesta en el siguiente párrafo, intenta adivinarla antes de seguir leyendo. Si tuviera una fursona, este sería el lugar para algún arte]

La respuesta es llamar a un createFromParcel() no relacionado que en realidad modifica los datos brutos del LazyValue que se almacena bajo una clave diferente y que se pasará textualmente al siguiente proceso

Tenemos una implementación de createFromParcel() que en realidad puede llamar a writeInt() en el Parcel proporcionado

Pero no debido a que writeInt esté colocado por error, sino debido a la reflexión sin restricciones. En particular, dentro de PackageParser tenemos el siguiente código:```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.
}

Tenemos un constructor que llama a writeInt(0) en el Parcel proporcionado, sin embargo hay algunas cosas que complican la explotación.

En primer lugar, aunque no es directamente visible en el código fuente, inmediatamente después de que se llama a newInstance(), se realiza una conversión y se lanza una ClassCastException.

Tragar la Excepción

Necesitaba algo que durante createFromParcel llamara a createFromParcel de otra clase dentro de un bloque try y luego fallara al propagar la Excepción capturada.

Esta es la parte donde el exploit en realidad no funciona en AOSP puro; he usado una clase específica de Samsung.

He incluido una copia de las partes relevantes de esa clase en este repositorio.

Este repositorio también incluye un script que lo integra en AOSP, así que para probarlo puedes ejecutarlo (pasa la ruta a tu checkout de AOSP como argumento, p. ej. ./make-aosp-buggy.sh /path/to/aosp), revierte el cambio descrito al inicio del informe y ejecuta este exploit contra tu compilación de AOSP.

Anteriormente usé la clase OutputConfiguration de AOSP para tragar Excepciones; antes de Android 13, tragar una Excepción en createFromParcel() combinado con permitir la construcción de otros Parcelable-s es en sí mismo una vulnerabilidad, sin embargo en el caso de SemImageClipData el tragado de Excepciones no estaba presente en esas versiones de Android.

Hay sin embargo una diferencia importante entre SemImageClipData y el OutputConfiguration usado anteriormente: aunque SemImageClipData captura una Excepción, aún devuelve un objeto no nulo y si luego se convierte a otro tipo, eso desencadenaría una ClassCastException, que es lo que estamos tratando de evitar.

El Borrado de Tipos de Java contraataca

El Borrado de Tipos de Java significa que los métodos genéricos en realidad no conocen el tipo genérico usado por el llamador. Esto normalmente ayudaba a la explotación.```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:~
Esta vez la eliminación de tipos no jugó a nuestro favor. Primero teníamos un método que en realidad invocaba al constructor a través de la reflexión.```java
private static <T extends IntentInfo> ArrayList<T> createIntentsList(Parcel in) {
    // ...
    final ArrayList<T> intentsList;
    // ...
    intentsList.add(cons.newInstance(in));
    // ...
    return intentsList;
}

Este método tiene un parámetro genérico T. No importa qué tipo de parámetro haya utilizado el llamador; sin embargo, dado que en la declaración de este método existe <T extends IntentInfo>, la línea con la llamada a newInstance() se convierte en intentsList.add((IntentInfo) cons.newInstance(in));, aunque newInstance() devuelve Object y ArrayList.add() acepta Object como argumento. Esto introdujo la necesidad de envolver la llamada a ese método con algún Parcelable que absorba la Exception.

Luego tenemos la llamada 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:~
El procedimiento de deserialización se realiza mediante la llamada a `getValue()`, que en realidad conduce a la llamada a `createFromParcel()`. Si ocurre un `ClassCastException` allí, no será capturado. `getValue()` ahora devuelve cualquier valor que se haya deserializado para esta clave mediante [`parcel.readValue()`](https://developer.android.com/reference/android/os/Parcel#readValue(java.lang.ClassLoader))

Sin embargo, si ponemos `SemImageClipData` como valor, dentro del bloque `try`-`catch` intentaríamos convertirlo a `T`, que en este caso es `Parcelable` tal como se declara en la declaración genérica del método. El llamador usa este método como genérico con `T` siendo un `Intent`, sin embargo `getParcelable()` no lo sabe y la conversión a `Intent` ocurre en el llamador y, por lo tanto, se lanza `ClassCastException` fuera del `try`

Podemos, sin embargo, envolver nuestro `SemImageClipData` dentro de un array `Parcelable[]`, entonces la conversión a `T` dentro de `getParcelable()` fallará al convertir `Parcelable[]` a `Parcelable` y lanzará `ClassCastException` dentro del `try`, esa `Exception` será registrada y se devolverá `null` que luego será aceptado por `checkKeyIntent()`

# El Diseño

Así que ahora necesitamos alinear el contenido dentro del `Bundle` para que después del ciclo `writeToParcel`/`createFromParcel` su contenido sea el que hemos preparado

Pero a diferencia del típico "`Bundle` FengShui" donde el desencadenante es que `createFromParcel` lea más o menos datos de los que `writeToParcel` escribió previamente, aquí tenemos `writeInt(0)` sobrescribiendo parte del `LazyValue` no deserializado

Así que así es como se ve `Bundle.mParcelledData` cuando se desempaca por primera vez por `AccountManagerService` (Los desplazamientos se tomaron llamando a `dataPosition()` a través del depurador adjunto a `system_server`)

<table>
<tr><th>Desplazamiento</th><th>Valor</th><th>Nota</th></tr>
<tr><td>0</td><td>3</td><td>Número de pares clave-valor</td></tr>
<tr><td>4</td><td>"intent"</td><td>Primera clave en el <code>Bundle</code>, la que será accedida por <code>getParcelable(AccountManager.KEY_INTENT)</code></td></tr>
<tr><td>24</td><td>16</td><td>El primer <code>LazyValue</code> comienza aquí, el tipo es <code>VAL_PARCELABLEARRAY</code></td></tr>
<tr><td>28</td><td>340</td><td>Longitud declarada del <code>LazyValue</code>, usada para encontrar la siguiente clave en el <code>Bundle</code>. Nuestro <code>LazyValue</code> en realidad no tendrá este tamaño después de ser leído, pero <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">informa de eso a través de <code>Slog.wtfStack()</code></a> que <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Slog.java;l=230-235;drc=4d6b008243a5b1b1fb4e725e37e14651a24a4a4d">no lanza excepción</a></td></tr>
<tr><td>32</td><td>1</td><td>Longitud del array <code>Parcelable[]</code>, el array tiene solo un elemento y está presente para que el <code>ClassCastException</code> ocurra dentro del bloque <code>try</code> que está en <code>bundle.getParcelable()</code></td></tr>
<tr><td>36</td><td>"com.samsung.android.<br>content.clipboard.data.<br>SemImageClipData"</td><td>Nombre de la clase <code>Parcelable</code>, esta es la clase contenedora que absorberá la Exception</td></tr>
<tr><td>160</td><td>2</td><td>Etiqueta de tipo usada por <code>createClipBoardData()</code></td></tr>
<tr><td>164</td><td></td><td>Elementos que son leídos por el constructor de la superclase <code>SemImageClipData</code> (que incluyen la llamada a <code>readParcelable()</code>, sin embargo eso ocurre fuera del bloque <code>try</code>). No son realmente relevantes, pero necesitamos pasar por ellos antes de llegar a la parte interesante de <code>createFromParcel()</code></td></tr>
<tr><td>252</td><td></td><td>Datos leídos por <code>SemImageClipData.readFromSource()</code></td></tr>
<tr><td>272</td><td>"android.content.pm.<br>PackageParser&#36;Activity"</td><td>Nombre del <code>Parcelable</code> leído por <code>mExtraParcelFd = in.readParcelable()</code>. El tipo no coincide, sin embargo antes de que ocurra la conversión se lanzará una Exception de todos modos</td></tr>
<tr><td>360</td><td></td><td>Campos <code>className</code> y <code>metadata</code> de <code>PackageParser$Component</code></td></tr>
<tr><td>368</td><td>1</td><td>Número de elementos en <code>createIntentsList()</code></td></tr>
<tr><td>372</td><td>"android.os.<br>PooledStringWriter"</td><td>Nombre de la clase que será instanciada mediante <code>Class.forName().getConstructor(Parcel.class).newInstance()</code>. En esta posición termina el primer <code>LazyValue</code>, sin embargo el análisis de este continúa ya que <code>readValue()</code> no llegó al final. También se interpreta como la segunda clave en el <code>Bundle</code> durante el <code>unparcel()</code> inicial</td></tr>
<tr><td>436</td><td>4</td><td>El segundo <code>LazyValue</code> comienza aquí, este 4 es <code>VAL_PARCELABLE</code> para el cual <code>Parcel.isLengthPrefixed()</code> devolverá <code>true</code>. Este valor es posteriormente sobrescrito por el constructor de <code>PooledStringWriter</code>, después de lo cual se lanza una Exception y <code>getParcelable(AccountManager.KEY_INTENT)</code> termina</td></tr>
<tr><td>440</td><td>240</td><td>Longitud del <code>LazyValue</code> cuyo tipo fue declarado como <code>VAL_PARCELABLE</code>, esto se usa para determinar la posición de la siguiente entrada y cuántos datos necesitan ser copiados al <code>Bundle</code> de destino durante la re-serialización. Este <code>LazyValue</code> no se desempaca realmente y se usa como contenedor de datos sin procesar</td></tr>
<tr><td>684</td><td>"1&y~pw"</td><td rowspan="2">Tercer par clave-valor, la clave se genera aleatoriamente para tener un <code>hashCode()</code> de Java superior a los usados anteriormente (Los elementos almacenados dentro de <code>ArrayMap</code> se ordenan por <code>hashCode()</code> ascendente de la clave y ese es el orden en que los elementos del <code>Bundle</code> se escribirán en el <code>Parcel</code>). Este par clave-valor está presente aquí solo para aumentar el número total de pares escritos, ya que ese será el número de pares leídos, aunque este par en realidad no será leído</td></tr>
<tr><td>704</td><td>-1 (<code>VAL_NULL</code>)</td></tr>
</table>

Entonces, cuando el Bundle se serializa de nuevo, se ve así:

<table>
<tr><th>Desplazamiento</th><th>Valor</th><th>Nota</th></tr>
<tr><td>0</td><td>3</td><td>Número de pares clave-valor</td></tr>
<tr><td>4</td><td>"intent"</td><td>Primera clave en el <code>Bundle</code></td></tr>
<tr><td>24</td><td>16</td><td><code>VAL_PARCELABLEARRAY</code>, el array <code>Parcelable[]</code> previamente deserializado ahora se está serializando de nuevo</td></tr>
<tr><td>28</td><td>196</td><td>Longitud del <code>LazyValue</code>, que es nuestro objeto <code>SemImageClipData</code> envuelto. Esta longitud se toma de la ejecución con mi <code>SemImageClipData</code> simulado y, por lo tanto, los desplazamientos presentados desde este punto no coincidirán con los que aparecerían en un dispositivo Samsung real, sin embargo este <code>LazyValue</code> no se deserializará de nuevo, por lo que eso no importa para la ejecución del exploit</td></tr>
<tr><td>224</td><td>"android.os.<br>PooledStringWriter"</td><td>Segunda clave en el <code>Bundle</code></td></tr>
<tr><td>288</td><td>0</td><td>El segundo <code>LazyValue</code> comienza aquí, el elemento bajo la clave <code>"android.os.PooledStringWriter"</code> no fue accedido, por lo que este <code>LazyValue</code> se está copiando de los datos originales, sin embargo la etiqueta de tipo fue sobrescrita por la llamada a <code>writeInt(0)</code> realizada por el constructor de <code>PooledStringWriter</code> y al llegar al proceso de destino esto ya no se interpreta como un <code>LazyValue</code></td></tr>
<tr><td>292</td><td>240</td><td>Esta era la longitud del segundo <code>LazyValue</code> que se copió del <code>Bundle</code> original, sin embargo dado que la etiqueta de tipo fue sobrescrita con <code>writeInt(0)</code>, que es <code>VAL_STRING</code>, este valor ahora se está leyendo mediante <code>readString()</code>. Anteriormente, para <code>LazyValue</code>, la longitud se expresaba en bytes, pero ahora, para <code>String</code>, se expresa en caracteres de dos bytes. No hay suficientes datos en el <code>Parcel</code> de origen para eso, por lo que <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/binder/Parcel.cpp;l=2221-2226;drc=4d6b008243a5b1b1fb4e725e37e14651a24a4a4d">el <code>parcel->readString16Inplace()</code> nativo falla después de leer la longitud</a>, eso sin embargo no causa una Exception en el lado de Java</td></tr>
<tr><td>296</td><td>"intent"</td><td>Clave "tercera" en el <code>Bundle</code>. En realidad sobrescribe la primera clave: dado que <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" tiene un <code>hashCode()</code> menor que la clave vista anteriormente, el método <code>ArrayMap.append()</code> usa <code>put()</code> que permite reemplazar valores</a>, de lo contrario <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/ArrayMap.java;l=667-675;drc=584140c83a456b5de99880b440c2d5dfc3c70506">tendríamos una clave duplicada que luego sería rechazada por <code>validate()</code></a></td></tr>
<tr><td>316</td><td>4</td><td><code>VAL_PARCELABLE</code>, aquí comienza el <code>LazyValue</code> que contiene el <code>Intent</code> real que se iniciará</td></tr>
<tr><td>536</td><td>"1&y~pw"</td><td>Elemento de relleno que se escribió pero no se lee porque los 3 pares clave-valor ya fueron leídos. <a href="https://cs.android.com/android/_/android/platform/system/tools/aidl/+/96a02f50fdfa4d20aa46ae2dde927257eac46d4a">A diferencia de las interfaces AIDL</a>, no se realiza una comprobación <code>enforceNoDataAvail()</code> en el <code>Bundle</code> (pero incluso si la hubiera, <a href="https://github.com/michalbednarski/ReparcelBug2/issues/3">podría omitirse insertando una entrada ficticia que especifique la longitud esperada</a>)</td></tr>
</table>

# Cómo sucedió dos veces

Discutamos ahora cuatro parches, dos de los cuales corrigen la vulnerabilidad sobre la que trata este informe

* CVE-2023-20944 ([boletín](https://source.android.com/docs/security/bulletin/2023-02-01#framework), [parche](https://android.googlesource.com/platform/frameworks/base/+/d0bc9026e2e62e09fa88c1bcbf1dc1c3fb001375%5E%21/)): Esta es otra vulnerabilidad encontrada por mí. De manera similar a esta, el parche no hace evidente cómo se explotaría, pero [parece que otra persona lo ha descubierto (publicación de blog en chino)](https://konata.github.io/posts/creator-mismatch/)
* CVE-2023-21098 ([boletín](https://source.android.com/docs/security/bulletin/2023-04-01#framework), [parche](https://android.googlesource.com/platform/frameworks/base/+/107e6377328486fca55131ea06ca9d6a3c1585e0%5E%21/)): Esta es la primera vez que informé del exploit presentado aquí. Ese parche también introduce una corrección para la omisión de `checkKeyIntentParceledCorrectly()` que es aplicable a versiones de Android anteriores a la 13
* CVE-2023-35669 ([boletín](https://source.android.com/docs/security/bulletin/2023-09-01#framework), [parche](https://android.googlesource.com/platform/frameworks/base/+/f810d81839af38ee121c446105ca67cb12992fc6%5E%21/)): Este no es en respuesta a mi informe, pero creo que se hizo para corregir el mismo problema que CVE-2023-20944, pero para casos donde `AccountManager.KEY_INTENT` se lanza desde Activities distintas de `ChooseTypeAndAccountActivity` (por ejemplo [`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), que omití al informar el error la primera vez). Este cambio reemplazó el uso de `bundle.getParcelable()` tipado por el uso del no tipado y la comprobación manual de `getClass() != Intent.class`, lo que en realidad revirtió la corrección para CVE-2023-21098
* CVE-2023-45777 ([boletín](https://source.android.com/docs/security/bulletin/2023-12-01#framework), [parche](https://android.googlesource.com/platform/frameworks/base/+/f4644b55d36a549710ba35b6fb797ba744807da6%5E%21/)): Esta es la segunda vez que informé de este exploit. El parche mantuvo la comprobación manual de `getClass() != Intent.class`, pero además de eso trajo de vuelta el uso de `bundle.getParcelable()` tipado, que es una buena manera de corregir ambos problemas

Mientras que el mismo exploit funciona tanto para CVE-2023-21098 como para CVE-2023-45777, la forma en que logró omitir `checkKeyIntentParceledCorrectly()` difiere

En el caso de CVE-2023-21098, [`checkKeyIntent()` en realidad no se llamaba si el `Bundle` verificado no tenía 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). Como `checkKeyIntent()` es lo que llama a `checkKeyIntentParceledCorrectly()`, en el caso de que el `Bundle` original no pareciera contener un `Intent`, el `Bundle` después de la re-serialización no se verificaba

En el caso de CVE-2023-45777, `checkKeyIntentParceledCorrectly()` se llamaba correctamente, sin embargo [`writeBundle()` ocurría allí antes de la llamada a `getParcelable()` sin argumento de tipo (hasta la cual el `Bundle` no cambiaba su contenido)](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)
Descargar herramienta