Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
ReparcelBug2 — # Writeup und Exploit für installierte App zur System-Privilegienerweiterung auf Android 12 Beta durch CVE-2021-0928, eine `writeToParcel`/`createFromParcel`-Serialisierungsabweichung in `OutputConfiguration` | Kitploit
Tools/GitHubGitHub/michalbednarski/reparcelbug2
Android-SicherheitPrivilege EscalationSchwachstellenanalyseExploitationMobile SicherheitBinäranalyse
GitHubmichalbednarski/reparcelbug2

ReparcelBug2

# Writeup und Exploit für installierte App zur System-Privilegienerweiterung auf Android 12 Beta durch CVE-2021-0928, eine `writeToParcel`/`createFromParcel`-Serialisierungsabweichung in `OutputConfiguration`

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen
Repository anzeigen
1252454vor 4 JahrenVon Kitploit geprüft

CVE-2021-0928, writeToParcel/createFromParcel-Serialisierungsfehlanpassung in android.hardware.camera2.params.OutputConfiguration

Dies ist ein Exploit, der diese Schwachstelle zur Privilegienerweiterung von einer installierten Android-App in die Android-Einstellungen-App nutzt (oder in jede andere installierte App, an die die App an ein in AndroidManifest.xml deklariertes <receiver> senden konnte; eine Privilegienerweiterung durch Senden an <activity> war ebenfalls möglich, wird hier jedoch nicht dargestellt)

Ich habe das Problem ursprünglich in Android 12 Developer Preview 3 gefunden

Die in diesem Repository vorhandene Exploit-Version funktioniert auf Android 12 Beta 2 und 3

Die Schwachstelle wurde im ersten offiziellen Android-12-Release behoben

Der folgende Writeup wurde ursprünglich für Google verfasst, um diesen Bericht als vollständige Exploit-Kette zu berücksichtigen

Zum Zeitpunkt der Erstellung war Android 12 nicht in AOSP verfügbar (Android Developer Preview/Beta-Releases sind nicht Open Source)

Screenshot der Android-Benachrichtigung aus der Einstellungen-App: Hallo von 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

Einführung in Parcel

Der Großteil der IPC-Kommunikation auf Android erfolgt über die Klasse Parcel

Die grundlegende Verwendung von Parcel ist wie folgt:```java Parcel p = Parcel.obtain(); p.writeInt(1); p.writeString("Hello");

root@kitploit:~
Dann wird `Parcel` an einen anderen Prozess [über `Binder`](https://developer.android.com/reference/android/os/IBinder#transact(int,%20android.os.Parcel,%20android.os.Parcel,%20int)) gesendet. Alternativ kann man zum Testen [`p.setDataPosition(0)`](https://developer.android.com/reference/android/os/Parcel#setDataPosition(int)) aufrufen, um den Parcel an den Anfang zurückzuspulen und mit dem Lesen zu beginnen:```java
int a = p.readInt(); // a = 1
String b = p.readString(); // b = "Hello"

Es ist zu beachten, dass Parcel intern eine Position verwaltet, von der aus Lesevorgänge durchgeführt werden. Es liegt in der Verantwortung des Benutzers der Parcel-Klasse sicherzustellen, dass die read*-Methoden mit den zuvor verwendeten write*-Methoden übereinstimmen, andernfalls erfolgen nachfolgende Lesevorgänge von falschen Positionen im Puffer.

Parcel bietet außerdem die Möglichkeit, benutzerdefinierte Objekte zu schreiben. Der bevorzugte Weg hierfür ist die Implementierung des Parcelable-Interfaces.

Hier ist eine Beispielimplementierung des Parcelable-Interfaces (irrelevanter Code entfernt, die WindowContainerTransaction-Klasse wird im Exploit als Teil der Gadget-Kette verwendet, jedoch gibt es daran nichts auszusetzen)```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:~
Wie oben zu sehen ist, wird die Methode `writeToParcel()` beim Schreiben verwendet. Beim Lesen wird dann die Factory-Methode `CREATOR.createFromParcel()` aufgerufen. Es liegt in der Verantwortung der `Parcelable`-Implementierung sicherzustellen, dass `createFromParcel` die gleiche Datenmenge liest, die von `writeToParcel` geschrieben wurde, da sonst alle nachfolgenden Lesevorgänge aus diesem `Parcel` Daten von einer falschen Position lesen.

Eine solche Klasse kann wie folgt in einen `Parcel` geschrieben bzw. daraus gelesen werden:

* Durch direkten Aufruf von `obj.writeToParcel(parcel, 0)` / `obj = WindowContainerTransaction.CREATOR.createFromParcel()`. Dies wird häufig verwendet, wenn der Typ der Klasse bekannt ist, z. B. wenn ein `Parcelable` ein Feld mit einem anderen `Parcelable` hat oder in von AIDL generiertem Code, wenn eine definierte RPC-Methode ein `Parcelable` als Argument hat
* Über `Parcel.writeParcelable`/`readParcelable`. [`writeParcelable`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=1909;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3) schreibt zuerst den Namen der Klasse und ruft dann die Methode `writeToParcel` aus dem `Parcelable`-Interface auf. [`readParcelable`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=3282;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3) liest den geschriebenen Klassennamen, findet die Klasse mit diesem Namen im bereitgestellten `ClassLoader` oder im `BOOTCLASSPATH`, falls null bereitgestellt wurde. Sobald die Klasse gefunden ist, wird ihr statisches Feld `CREATOR` verwendet, um eine [`Parcelable.Creator`](https://developer.android.com/reference/android/os/Parcelable.Creator)-Instanz zu erhalten, die als Factory zum Lesen dieser Klasse dient. Es ist wichtig zu beachten, dass bei Verwendung der Methode `readParcelable` jedes beliebige `Parcelable` gelesen werden kann, das im Klassenpfad verfügbar ist, da der Name des zu erstellenden Objekts aus demselben `Parcel` gelesen wird
* `readParcelable` wird von vielen anderen `Parcel`-Methoden verwendet, z. B. liest `readList` aus dem obigen Beispiel Elemente über `readValue`, die generischste Methode zum Übertragen von Objekten in einem `Parcel`, und eine ihrer Verwendungsarten ist über `readParcelable`. Auch im obigen Beispiel kann das Feld `ArrayList<HierarchyOp> mHierarchyOps` aufgrund der Java-Typ-Erasure tatsächlich beliebige Objekte enthalten, die von Parcel unterstützt werden, nicht nur solche, die mit dem in der generischen Typdeklaration angegebenen Typ kompatibel sind

# `writeToParcel`/`createFromParcel`-Abweichungen

Wie oben erwähnt, liegt es in der Verantwortung der `Parcelable`-Interface-Implementierung sicherzustellen, dass `createFromParcel` die gleiche Datenmenge aus dem `Parcel` liest, die das zugehörige `writeToParcel` zuvor geschrieben hat. Wann immer sich im `BOOTCLASSPATH` ein `Parcelable` befindet, das diesen Vertrag verletzen kann, entsteht eine Schwachstelle, da sie das folgende Szenario ermöglicht:

1. Eine bösartige App sendet an `system_server` ein `Bundle` ODER ein `Parcelable`, das eine fehlerhafte `Parcelable`-Instanz zusammen mit speziell konstruierten Daten enthält, die tatsächlich in Schritt 3 gelesen, aber in Schritt 2 unverändert durchgereicht werden
2. `system_server` verifiziert, dass das `Bundle` sicher ist, und leitet es dann weiter ODER `system_server` übergibt das bereitgestellte `Parcelable` an eine AIDL-Methode, die auch kritische Daten im nächsten Parameter erhält (wenn die in diesem Parameter empfangenen Daten geändert werden könnten, würde dies ein Sicherheitsproblem verursachen)
3. Eine andere App empfängt Daten von `system_server` und vertraut ihnen, aber aufgrund der fehlerhaften Serialisierung unterscheiden sich die tatsächlich sichtbaren Daten von den Daten, die `system_server` senden wollte

Ich habe in den obigen Schritten "ODER" verwendet, da diese Schritte sowohl eine [alte Exploit-Variante beschreiben, die zum Starten einer beliebigen Activity führt und die ich 2017 veröffentlicht habe](https://github.com/michalbednarski/ReparcelBug) (auf der linken Seite von "ODER") als auch eine neue Variante, die ich hier im nächsten Abschnitt beschreiben werde

# Wie `BroadcastReceiver` in der App ausgeführt wird

Aus Sicht des Anwendungsentwicklers, der APIs aus dem Android SDK verwendet, funktioniert [`BroadcastReceiver`](https://developer.android.com/guide/components/broadcasts) so, dass eine Anwendung [`sendBroadcast`](https://developer.android.com/reference/android/content/Context#sendBroadcast(android.content.Intent)) aufruft (obwohl Apps oft Broadcasts vom System empfangen möchten, nicht von einer App) und dann wird das gesendete Intent mit den in `AndroidManifest.xml` definierten `<receiver>`-Elementen abgeglichen. Wenn dies geschieht, startet das System den Prozess der empfangenden Anwendung, instanziiert die `BroadcastReceiver`-Unterklasse, wie im Attribut `<receiver android:name>` definiert, und ruft dann die Methode [`onReceive`](https://developer.android.com/reference/android/content/BroadcastReceiver#onReceive(android.content.Context,%20android.content.Intent)) auf

Werfen wir einen Blick auf die Kommunikation mit `system_server`, die im Prozess stattfindet, der den Broadcast empfängt:

* Wenn der Anwendungsprozess zunächst gestartet wird, [ruft er `IActivityManager.attachApplication()` auf](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=7340;drc=f53e23b917aa0f6a6310e46a233a29b6d6226b2c). Dadurch übergibt er ein [`IApplicationThread`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/IApplicationThread.aidl)-Handle, das vom System verwendet wird, um dem Anwendungsprozess mitzuteilen, was zu tun ist
* Wenn das System einen im Manifest registrierten `BroadcastReceiver` im Anwendungsprozess ausführen möchte, ruft es die Methode [`scheduleReceiver`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=950;drc=f53e23b917aa0f6a6310e46a233a29b6d6226b2c) unter Verwendung des im vorherigen Punkt beschriebenen `IApplicationThread` auf. Diese Methode hat mehrere Argumente, aber die wichtigsten hier sind die ersten beiden:
  1. `Intent intent`, das zuvor an das System übergeben wurde, als `sendBroadcast()` aufgerufen wurde
  2. `ActivityInfo info`, das Informationen über die Komponente enthält, die ausgeführt werden muss. Der Wert für diesen Parameter wird vom System vom Package Manager Service übernommen. Am wichtigsten ist, dass die in diesem Parameter übergebenen Daten den Pfad zur Datei enthalten, aus der die Java-Klasse geladen wird, die den empfangenen Broadcast verarbeitet

An diesem Punkt können Sie wahrscheinlich erraten, was dieser neue Exploit-Pfad ist: Rufen Sie `sendBroadcast()` auf und übergeben Sie ein `Intent`, das dazu führt, dass beim Versuch des Systems, `scheduleReceiver` aufzurufen, die Anwendung, in der `scheduleReceiver` aufgerufen wird, ein manipuliertes `ActivityInfo` sieht

Es sollte beachtet werden, dass dieser neue Exploit-Pfad in Android 12 möglich wurde, da es zuvor keine Möglichkeit gab, beliebige `Parcelable`s in ein `Intent` zu legen ([Intent-Extras](https://developer.android.com/reference/android/content/Intent#putExtra(java.lang.String,%20android.os.Parcelable)) zählen nicht, da sie in ein `Bundle` gelegt werden, dessen gesamte Länge in den Parcel geschrieben wird und das [als einzelner Block gelesen wird](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/BaseBundle.java;l=1671-1681;drc=5d123b67756dffcfdebdb936ab2de2b29c799321), sodass Extras keine Fehlinterpretation des `Intent`-Objekts verursachen können, das sie enthält)

# Auslösen einer `writeToParcel`/`createFromParcel`-Abweichung

Meistens sind `writeToParcel`/`createFromParcel`-Abweichungen Fälle, in denen in einer dieser Methoden eines der Felder vergessen oder doppelt geschrieben wird. In einem solchen Fall löst das Senden eines solchen Objekts immer eine Abweichung aus. (Meistens passiert das, wenn ein Objekt zwar `Parcelable` ist, aber nicht wirklich prozessübergreifend verwendet wird, da dies sonst bei normaler Nutzung schnell auffallen würde)

Diesmal war das jedoch nicht der Fall, und das Auslösen der Abweichung ist nicht offensichtlich

Werfen wir einen Blick auf die anfällige Klasse ([Original war hier](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/hardware/camera2/params/OutputConfiguration.java;drc=46c390a1c695e2dc458cb889e40559f259f60aed), Zeilen, die mit `// New in Android 12` markiert sind, wurden manuell hinzugefügt, da sie zum Zeitpunkt des Schreibens nicht in AOSP vorhanden waren) ([Hier ist der Commit, der die Schwachstelle ursprünglich eingeführt hat](https://android.googlesource.com/platform/frameworks/base/+/a69b1bc58b0838e06deefb190e226774a34671e6%5E%21/#F10), er wurde jedoch erst nach der Veröffentlichung von Android 12 veröffentlicht)```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
}

Also, was ist hier falsch und warum führt diese Änderung eine Schwachstelle ein? Wie ich bereits bei der Besprechung des WindowContainerTransaction-Beispiels Parcelable erwähnt habe, kann readList die Liste tatsächlich mit jedem Objekt füllen, das von Parcel unterstützt wird, nicht nur mit denen, die der generischen Deklaration entsprechen (ArrayList<Integer>). Da wir diese Klasse jedoch nur als Teil einer Serialisierungs-Gadget-Kette verwenden und sie nicht tatsächlich nutzen, wird dieses Feld für nichts anderes als das Lesen und Schreiben in Parcel verwendet (und Versuche, Elemente aus einer ArrayList zu verwenden, die Elemente enthält, die nicht ihrer generischen Deklaration entsprechen, würden ohnehin nur zu einer ClassCastException führen), ist das an sich kein Problem

In dieser Klasse gibt es auch ein try-catch innerhalb von createFromParcel, was bedeutet, dass wenn während des Lesens eine Exception ausgelöst wird, das Lesen von OutputConfiguration gestoppt wird und das Lesen des Objekts, das OutputConfiguration enthält, fortgesetzt wird. Wenn das passiert, wird die gesamte OutputConfiguration in den Parcel geschrieben, aber sie wird nur bis zu dem Punkt gelesen, an dem die Exception aufgetreten ist. Dies erzeugt eine Diskrepanz, da nicht verbrauchte Daten, die innerhalb von OutputConfiguration.writeToParcel geschrieben wurden, tatsächlich von dem Objekt gelesen werden, das OutputConfiguration.CREATOR.createFromParcel aufgerufen hat

Nun gibt die Kombination dieser beiden Dinge (das Zulassen der Verschachtelung beliebiger Objekte, die von Parcel unterstützt werden, und das Umschließen dieser mit try-catch ohne erneutes Auslösen) die Fähigkeit, ein Parcelable zu konstruieren, das von system_server geschrieben und später auf eine Weise gelesen werden kann, die von der App kontrolliert wird, die das Parcelable ursprünglich konstruiert hat und das von system_server weitergeleitet wird

Ok, um ein solches Parcelable zu konstruieren, müssen wir jetzt etwas finden, das in mSensorPixelModesUsed eingefügt werden kann, das erfolgreich in system_server gelesen wird (da dieses Objekt von der Angreifer-App über Parcel empfangen wird), erfolgreich von system_server geschrieben wird und dann beim Entpacken fehlschlägt und eine Exception in der Opfer-App auslöst

Eine Möglichkeit, dies zu tun, ist die Verwendung einer Klasse, die in system_server vorhanden ist, aber nicht in Apps, sodass der Versuch, sie zu deserialisieren, zu einer ClassNotFoundException führen würde. Ich kann jedoch kein Parcelable aus system_server auswählen, da readParcelable ohne explizit angegebenen ClassLoader nur den BOOTCLASSPATH durchsucht, der keine system_server-spezifischen Klassen enthält. Die Lösung für dieses Problem besteht darin, eine der Serializable-Klassen zu verwenden, da ObjectInputStream den ClassLoader von der ersten Nicht-BOOTCLASSPATH-Methode im Stack-Trace auswählt

Ich habe PackageManagerException ausgewählt, aber bevor wir es verwenden, gibt es noch eine Sache, die wir tun müssen. Im OutputConfiguration-Konstruktor, wenn readList aufgerufen wird, wird das loader-Argument explizit auf Integer.class.getClassLoader() gesetzt. Dieser loader-Wert wird an readValue() weitergegeben, dann an readSerializable() und innerhalb von readSerializable(), wenn der loader-Parameter nicht null ist, wird er anstelle von von verwendet (diese -Prüfung tut nichts, weil wenn die Klasse nicht findet, wirft es eine Exception, anstatt null zurückzugeben). Der Weg darum herum ist jedoch ziemlich einfach: Wir müssen nur in ein einwickeln, das ohne Angabe eines ausführt. Hier kommt die oben beschriebene -Klasse ins Spiel

An diesem Punkt haben wir also das folgende Objekt:

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

Nun kann ein solches Objekt erfolgreich innerhalb von system_server deserialisiert werden: Wenn WindowContainerTransaction readList aufruft, versucht es, die PackageManagerException-Klasse mit dem ClassLoader des Systemservers zu finden (nicht BootClassLoader), da es sie im Stack-Trace finden kann. Dieser Klassenlader ist zufällig im Stack-Trace vorhanden, denn während alle folgenden Methoden nicht aus dem Klassenpfad des Systemservers stammten: Binder#execTransact(), IActivityManager$Stub#onTransact(), generiert von AIDL, und Methoden aus allen verwendeten Parcelable-Klassen, gab es eine Methode, die innerhalb des Systemservers im Stack-Trace deklariert war: ein überschriebenes onTransact innerhalb von ActivityManagerService. Daher kann ein solches Objekt lesen und später in einen schreiben, und wenn die Ziel-App versucht, es zu lesen, ist die -Klasse nicht verfügbar und daher wird eine ausgelöst, und dann

Also haben wir diese Diskrepanz ausgelöst. Nun, noch nicht wirklich an diesem Punkt, weil die Exception abgefangen wurde, als keine ungelesenen Daten von OutputConfiguration.writeToParcel übrig waren, aber wir können leicht ein weiteres Element zur List mSensorPixelModesUsed hinzufügen, und dieses Element wird durch Parcel.writeValue geschrieben und nach dem Lesen von OutputConfiguration ungelesen gelassen

Einfügen in Intent

Wie oben erwähnt, möchte ich die Diskrepanz von einem Intent-Objekt aus auslösen, da es von system_server an eine AIDL-Methode übergeben wird, die Intent im ersten Parameter und Ausführungsinformationen im zweiten Parameter hat, sodass die Serialisierung/Deserialisierung des im ersten Parameter übergebenen Intent zur Änderung des Werts im zweiten Parameter führt

In Intent.readFromParcel() werden alle Werte über dedizierte typisierte Methoden gelesen, daher können wir dort keine benutzerdefinierte Parcelable-Klasse angeben

Innerhalb von Intent gibt es jedoch ein verschachteltes ClipData, und seit Android 12 gibt es in ClipData$Item ein neues Feld ActivityInfo mActivityInfo (war zum Zeitpunkt des ursprünglichen Schreibens nicht in AOSP vorhanden, hier ist der Commit, der dieses Feld einführt, dieses Feld wird durch in.readTypedObject(ActivityInfo.CREATOR) im ClipData(Parcel in)-Konstruktor gelesen)

Dann gibt es innerhalb des ActivityInfo(Parcel source)-Konstruktors wiederum keine Möglichkeit, ein benutzerdefiniertes Parcelable einzufügen, aber da ActivityInfo von ComponentInfo erbt, hat es ein applicationInfo-Feld

Schließlich gibt es in ApplicationInfo ein SparseArray<int[]> splitDependencies-Feld, das durch readSparseArray gelesen wird, das wiederum readValue verwendet, um SparseArray-Elemente zu lesen

An diesem Punkt könnten wir OutputConfiguration innerhalb von splitDependencies platzieren, aber das Lesen von splitDependencies wird von einigen readString8()-Aufrufen gefolgt, und es wäre schön, die volle Kontrolle über nicht verbrauchte Daten nach dem Auftreten der Diskrepanz zu haben, damit wir dort direkt leere Strings platzieren können und uns keine Gedanken über die unterschiedliche Interpretation nicht verbrauchter Daten machen müssen

Dazu müssen wir zuerst einen Rohdatencontainer innerhalb von OutputConfiguration.mSensorPixelModesUsed platzieren, der durch writeValue geschrieben wird. Ich habe Bundle gewählt. Auf diese Weise haben wir in den nicht verbrauchten Daten Folgendes übrig:

  1. writeValue VAL_BUNDLE-Tag
  2. Länge der Rohdaten (dieser Link gilt auch für die übrigen Elemente in dieser Liste)
  3. BUNDLE_MAGIC
  4. Rohdaten, die unverändert durch Parcel.appendFrom übergeben werden

Wir haben also drei Parcel.writeInt-Elemente, die wir nicht verbraucht hätten. Wir können sie loswerden, indem wir OutputConfiguration in ein Parcelable einwickeln, das beim Lesen einen beliebigen Parcelable-Wert gefolgt von drei Ints liest. Ich habe das in ZenPolicy CREATOR gefunden

Zusammenfassend haben wir die folgende Objekthierarchie (die in system_server vorhanden ist und die es versucht, an scheduleReceiver zu übergeben)

  • 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

Das wird von system_server geschrieben. Dann liest die empfangende Anwendung alles bis zu (und einschließlich der readSerializable-Daten von) PackageManagerException normal, aber nachdem die Serializable-Daten für PackageManagerException gelesen wurden, wird eine Exception ausgelöst und das Lesen von allem unterhalb von OutputConfiguration wird abgebrochen, sodass Bundle ungelesen bleibt. Das Lesen fährt mit ZenPolicy fort, das die drei Ints verbraucht, die den Rohdaten innerhalb von Bundle vorausgehen. Dann fährt das Lesen von ApplicationInfo mit dem Lesen von Daten fort, die zuvor Rohdaten waren, die unverändert in Bundle übergeben wurden. Das Lesen dieser Rohdaten wird mit den verbleibenden Objekten in diesem Stack fortgesetzt (ApplicationInfo, ActivityInfo, und ), und dann werden diese Rohdaten zum Lesen des nächsten -Methodenparameters verwendet

Was dann innerhalb von handleReceiver passiert

Wie ich gerade gesagt habe, werden die verbleibenden scheduleReceiver-Parameter nun aus einem Puffer gelesen, der vom Angreifer kontrolliert wird.

Schauen wir uns an, was passiert, sobald diese Methode aufgerufen wird.

Zuerst packt scheduleReceiver Werte aus allen Argumenten und verwendet sendMessage(), um die Ausführung an den Hauptthread zu übergeben

Als Nächstes wird im Hauptthread handleReceiver aufgerufen

handleReceiver ruft getPackageInfoNoCheck auf und übergibt ihm ApplicationInfo, das es als Teil von ActivityInfo erhalten hat, das an das scheduleReceiver-Argument übergeben wurde

getPackageInfo prüft, ob das Paket mit dem angegebenen Namen bereits im Cache vorhanden ist, und wenn nicht, konstruiert es eine neue LoadedApk-Instanz und übergibt ihr das zuvor erhaltene ApplicationInfo-Objekt (Da der Angreifer verursachen möchte, dass ein neues LoadedApk konstruiert wird, wird ein packageName eines Pakets verwendet, das in diesem Prozess zuvor nicht gesehen wurde)

Dann wird die ContextImpl.getClassLoader()-Methode verwendet, die beim ersten Aufruf an mPackageInfo.getClassLoader() delegiert, wobei mPackageInfo ein LoadedApk ist, das im vorherigen Absatz konstruiert wurde

Dann gibt es createOrUpdateClassLoaderLocked, das makePaths aufruft, um zipPaths zu füllen mit Pfaden, die im ClassLoader verwendet werden sollen, dann werden sie verbunden und der zip-Variable zugewiesen und das wird an createClassLoader übergeben

makePaths füllt zipPaths mit Informationen aus ApplicationInfo, am wichtigsten ist dies sourceDir. Die Angreiferanwendung erstellt ein injiziertes ApplicationInfo mit sourceDir, das auf den Pfad zur eigenen APK gesetzt ist, daher wird die Empfängerklasse tatsächlich aus der Angreifer-APK geladen. Dies führt direkt zur Ausführung von angreiferkontrolliertem Code innerhalb der Anwendung, die den Broadcast empfängt

Hinweis zu Hidden-API-Prüfungen

Es gab noch eine weitere Sache, die umgangen werden musste: Hidden-API-Prüfungen. Diese waren nie als Sicherheitsgrenze gedacht (da eine Anwendung immer NDK verwenden und den zugrunde liegenden Syscall direkt aufrufen kann), aber in diesem Fall wurden sie umgangen, indem ein konstruiertes ClipData durch manuelles Schreiben von Daten in Parcel und anschließendes Verwenden von readParcelable erstellt wurde. Ein solches ClipData konnte dann normal an Intent angehängt und an sendBroadcast() übergeben werden, sodass das Senden des Broadcasts selbst nur mit öffentlichen APIs erfolgte

Fixes

Der obige Bericht wurde ursprünglich an Google gesendet, und es sieht so aus, als hätten sie ihn verwendet, da es mehrere Fixes gibt, die daraus resultieren (ich denke, ich habe keinen Beweis für direkte Kausalität)

Mit Android 12 veröffentlicht:

  • Das Schlucken von Exceptions aus OutputConfiguration und verwandten Klassen wurde entfernt
  • OutputConfiguration#mSensorPixelModesUsed wird nicht mehr durch writeValue geschrieben
  • ClipData#mActivityInfo wird nicht mehr in Parcel geschrieben, es sei denn, es wird beim Schreiben explizit angefordert (sodass Intent keine beliebigen Parcelables aus dem BOOTCLASSPATH mehr enthalten kann, wodurch diese Ausnutzungstechnik eliminiert wird)

Zum Zeitpunkt des Schreibens nur auf dem master-Zweig vorhanden, nicht in veröffentlichten Versionen, wird wahrscheinlich in Android 13 erscheinen (nicht in 12L):

  • Es gibt neue List-Lesemethoden auf Parcel, die den Typ der Elemente prüfen und untypisierte Versionen wurden als veraltet markiert
  • Es gibt eine neue Methode Parcel#enforceNoDataAvail(), die prüft, dass keine ungelesenen Daten im Parcel verbleiben, die anscheinend von AIDL nach dem Lesen der RPC-Aufrufargumente verwendet wird. Normalerweise verließen sich meine Exploits auf die Tatsache, dass nach dem Lesen aller Daten aus dem Parcel alles andere ignoriert wurde. Das wäre nicht mehr der Fall, obwohl ich denke, dass man in vielen Fällen Daten konstruieren könnte, die am Ende dazu führen, dass zur endgültigen Position gesprungen wird, also ist dies keine starke Abschwächung. Trotzdem treten solche Probleme manchmal natürlich unentdeckt auf, sodass diese Methode diese abfangen würde. Weitere Diskussion darüber befindet sich in Issue #3
  • Jedes Element in Bundle erhält seine Länge separat gespeichert. Dies tötet ziemlich die gesamte Bug-Klasse, von der ich seit 2014 privat Bugs an Google gemeldet habe, veröffentlichte Beschreibung 2017 und Code selbst etwa ein Jahr später. Wenn ich richtig zähle, werden das 8 Jahre Bug-Klassen-Lebensdauer sein (ich habe ehrlich gesagt keine Ahnung, ob das viel ist, obwohl es immer noch Nicht-Bundle-Exploit-Varianten geben könnte, wie genau diese (obwohl genau diese bereits behoben wurde))
Tool herunterladen
resolveClass
ObjectInputStream
c != null
Class.forName
PackageManagerException
Parcelable
readList
ClassLoader
WindowContainerTransaction
system_server
Parcel
PackageManagerException
ClassNotFoundException
in eine RuntimeException eingewickelt
vom OutputConfiguration-CREATOR abgefangen
ClipData
Intent
handleReceiver