Die Binder Character Device

Das Binder-Framework ist die Brücke zwischen zwei Welten: dem User-Space und dem Kernel-Space. Der Kernel-Space ist erforderlich, damit der Binder-Treiber Nachrichten an einen Prozess senden kann. Der User-Space hingegen ist notwendig, damit eine Anwendung mit dem Binder-Treiber kommunizieren kann. Der Binder-Treiber ist Teil des Android-Linux-Kernels und übernimmt viele Low-Level-Operationen. Der Treiber erstellt eine „character device file“. In älteren Android-Versionen war dies die Datei /dev/binder. In neueren Versionen wurde der Speicherort zu /dev/binderfs/binder geändert, wobei /dev/binder ein Symlink dorthin ist. Diese Datei dient als Gateway für die Prozesse im Benutzerbereich, um Befehle und Daten an den Treiber zu senden. Die ioctl-Befehle werden hierhin gesendet. Die Android-API bietet zahlreiche Abstraktionen in Form von Proxies und Managern, sodass Entwicklerinnen und Entwickler einen übergeordneten Ansatz für die Interprozesskommunikation (IPC) nutzen können, anstatt die ioctl-Nachrichten manuell zu erstellen.

Figur 1: Character Device des Binder Interfaces

Es ist zu beachten, dass die Datei /dev/binderfs/binder für die allgemeine Binder-Kommunikation verwendet wird, während die Datei /dev/binderfs/hwbinder für die hardwarespezifische Binder-Kommunikation zuständig ist. Die Datei /dev/binderfs/vndbinder wird für die herstellerspezifische Binder-Kommunikation verwendet. Auch wenn es verschiedene Binder-Gerätedateien gibt, kommunizieren alle mit demselben Kernel-Treiber.

Binder-Framework: Organisierung der Kommunikation

Die Kommunikation zwischen verschiedenen Prozessen kann über das Binder-Framework hergestellt werden, jedoch sollte dabei eine gewisse Strukturierung beachtet werden. In der Regel interagieren zahlreiche Anwendungen und Dienste gleichzeitig mit anderen Diensten, sodass eine gewisse Strukturierung erforderlich ist.

Das Binder-Framework verwendet Queue, um eingehende und ausgehende Nachrichten zu organisieren. Das Binder-Framework von Android verarbeitet Nachrichten in der Reihenfolge, in der sie empfangen wurden. Jede Android-App, die mit anderen Prozessen interagieren möchte, verfügt über eine eigene Queue. Tatsächlich hat sie sogar zwei!

Figur 2: Prozess-wartschlangen der Inter-Prozess Kommunikation des Binder Drives

Jeder Dienst oder jede Anwendung, die eine IPC-Anfrage sendet, verfügt über eine Queue für eingehende Nachrichten und eine Queue für ausgehende Nachrichten. Die ausgehende Queue verwaltet und speichert die Nachrichten, die von den anfragenden Prozessen – also den Anwendungen, die die Interprozesskommunikation angefordert haben – generiert werden. Die eingehende Queue hingegen sammelt und speichert alle Nachrichten von den Ziel-Diensten, die für den anfragenden Anwendungsprozess bestimmt sind. Jede Queue wird vom Binder-Treiber überwacht. Eine Nachricht wird als Transaktion gekapselt. Eine Transaktion enthält eine Parcel-Struktur.

Parcels

Parcels sind spezielle Datencontainer, die die eigentliche Nachricht und einige Metadaten speichern. Parcels dienen als Standardcontainer für die Übertragung verschiedener Datentypen über Prozessgrenzen hinweg. Wenn eine Anwendung eine IPC-Operation nutzt, kommt in der Regel ein Intent zum Einsatz. In der Android-Dokumentation heißt es, dass die Intent-Klasse die Schnittstelle Parcelable erweitert. Der Dokumentation zufolge stellt die Schnittstelle Parcelable sicher, dass Instanzen der Klasse in ein Parcel-Objekt geschrieben und daraus wiederhergestellt werden können.

Schnittstelle für Klassen, deren Instanzen in ein Parcel geschrieben und daraus wiederhergestellt werden können. Klassen, die die Parcelable-Schnittstelle implementieren, müssen außerdem über ein nicht-nulles öffentliches statisches Feld namens CREATOR verfügen, dessen Typ die Parcelable.Creator-Schnittstelle implementiert.

Figur 3: Die Klasse Intent wird, zusammen mit anderen, durch das Pacelable Interface erweitert

Bildquelle: https://developer.android.com/reference/android/content/Intent 

Parcel-Objekte sind Datenpuffer, die für die Übertragung über Android optimiert sind. Die Objekte in diesen Puffern unterstützen eine Reihe von primitiven Datentypen, z. B. Ganzzahlen, lange Ganzzahlen, Gleitkommazahlen, Double-Gleitkommazahlen und mehr. Parcel-Objekte unterstützen auch benutzerdefinierte Daten, wie z. B. von Entwicklerinnen und Entwicklern  erstellte Klassen. Damit eine benutzerdefinierte Klasse für ein Parcel geeignet ist, muss die Klasse um die Schnittstelle Parcelable erweitert werden. Die Schnittstelle Parcelable ist eine für Android optimierte Serialisierungsmethode.

Intents

Daten, die an einen anderen Prozess gesendet werden sollen, müssen in ein Parcel-Objekt umgewandelt werden. Das Parcel-Objekt selbst wird dann von einem anderen Objekt umhüllt, um gesendet zu werden. Eine der gängigsten Arten von Wrappern ist die Klasse Intent. Intents sind ein spezieller Nachrichtentyp und wahrscheinlich die gängigste Methode für die Interprozesskommunikation (IPC) innerhalb von Android. Intents werden für die Interaktion mit Android-Diensten, Anwendungen und Komponenten innerhalb von Anwendungen, wie beispielsweise Activities, verwendet.

Codeblock

class MainActivity : ComponentActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {

     super.onCreate(savedInstanceState)

     enableEdgeToEdge()

    setContent {

       CallerApplicationTheme {

        val mContext = LocalContext.current

        Surface {

           CallerScreen(mContext, Modifier.fillMaxSize())

        }

      }

    }

  }

}

 

@Composable

fun CallerScreen(mContext: Context, modifier: Modifier) {

  Column(

     horizontalAlignment = Alignment.CenterHorizontally,

     verticalArrangement = Arrangement.Center,

    modifier = modifier.padding(8.dp)

  ) {

    Text(

      "Tap the Button"

    )

    Button(

      onClick = {

        Intent( Intent.ACTION_MAIN).also {it ->

           it.component = ComponentName(

             "com.bdosecurity.test.calledapplication", 

             "com.bdosecurity.test.calledapplication.MainActivity" 

          ) 

           it.putExtra("keyWord", "Sent by Caller App") 

           mContext.startActivity(it)  

        }

    ) 

    { 

      Text("Open the 2nd App") 

    } 

  }

}

Figur 4: Aufrufen einer anderen Aktivität mithilfe von Intents (1/2)

Figur 5: Aufrufen einer anderen Aktivität mithilfe von Intents (2/2)

Nachdem sie tief in die Klassen und Methoden absteigen, die zum Starten der Aktivität verwendet werden, rufen die internen Komponenten Funktionen auf, die die Parcel-Objekte und die IBinder-Schnittstelle verarbeiten. Letztere dient als Schnittstelle für die Datei /dev/binderfs/binder.

Figur 6: Von Binder benutztes Parcel Objekt.

Security im Android-Binder-Framework

Im Android-Binder-Framework ist die Kommunikation zwischen verschiedenen Prozessen streng eingeschränkt, obwohl sie über einen zentralen Manager koordiniert wird. Aus Sicherheitsgründen gibt es im System nur einen einzigen Service-Manager, der als maßgebliche Kontrollinstanz fungiert. Dieser Service-Manager verhindert, dass nicht vertrauenswürdige Dienste eine Verbindung zu ihm herstellen, und gewährleistet so die Integrität der Interaktionen zwischen den Prozessen. Wenn der Binder eine Anfrage an einen Systemprozess weiterleitet, fügt er zudem Informationen über den aufrufenden Client-Prozess hinzu. Dienste können diese Daten dann nutzen, um zu überprüfen, ob die aufrufende Person berechtigt ist, bestimmte Funktionen des Zielprozesses aufzurufen. Ein „vertrauenswürdiger Dienst“ bezeichnet in diesem Zusammenhang eine Systemkomponente, der die Berechtigung erteilt wurde, mit anderen sicheren Diensten zu interagieren oder sensible Vorgänge auszuführen; dies wird in der Regel durch Signaturen auf Systemebene, Berechtigungen oder vordefinierte Zugriffskontrollrichtlinien ermöglicht.

Permissions

Berechtigungen in Android legen fest, welche Aktionen eine App oder ein Dienst ausführen darf, beispielsweise auf die Kamera zugreifen, Kontakte lesen oder mit privilegierten Systemdiensten interagieren. Diese Berechtigungen werden Apps auf Grundlage ihrer UID und der Angaben im Manifest erteilt, und das Binder-Framework nutzt die aufrufende UID und PID, um zu prüfen, ob ein Aufrufer auf eine bestimmte API zugreifen darf. Bei der Binder-basierten IPC werden Berechtigungen auf der Aufruferseite nicht einfach als vertrauenswürdig angesehen, sondern auf der Empfängerseite einer Binder-Transaktion stets überprüft. Ein Dienst ermittelt die Identität der Aufruferin bzw. des Aufrufers in der Regel mithilfe von Funktionen wie Binder.getCallingUid() und Binder.getCallingPid() und nutzt anschließend APIs wie Context.checkPermission oder enforceCallingPermission, um sicherzustellen, dass die aufrufende Person über die erforderliche Berechtigung verfügt, bevor er die angeforderte Operation ausführt. Ein vereinfachter Ablauf dieses Prozesses ist unten dargestellt:

Figur 7: Verifizierung der nötigen Berechtigungen für das anfordern eines Systemdienstes.

Das Binder-Framework überträgt nicht nur Methodenaufrufe zwischen Prozessen, sondern auch den Sicherheitskontext des Aufrufers, sodass Berechtigungsprüfungen auf der Seite des Aufgerufenen konsistent und zentralisiert erfolgen. Durch die Verknüpfung der UID/PID-Identität mit dem Berechtigungssystem stellt Binder sicher, dass nur autorisierte Aufruferinnen und Aufrufer privilegierte Funktionen aufrufen können, und bildet damit einen wesentlichen Bestandteil der mehrschichtigen Sicherheitsarchitektur von Android.

Binder as an Attack Surface

Trotz seines auf Sicherheit ausgerichteten Designs ist Binder ein großes und komplexes Kernel-Subsystem, das jeder nicht vertrauenswürdigen und selbst isolierten App zugänglich ist, was es zu einer besonders attraktiven Angriffsfläche macht. Die Sicherheitsforschung der letzten Jahre hat sich wiederholt auf die Verwaltung der Objektlebensdauer, die Referenzzählung und die internen Speicherzuordner von Binder konzentriert, wo subtile Fehler zu schwerwiegenden Kernel-Exploits führen können.

CVE‑2023‑20938 (use‑after‑free in Binder driver)

CVE-2023-20938 ist eine kritische „Use-after-free“-Sicherheitslücke (UAF) im Android-Binder-Kernel-Treiber, genauer gesagt in der Funktion binder_transaction_buffer_release in der Datei binder.c. Sie entsteht durch eine unsachgemäße Eingabevalidierung während der Bereinigung des Transaktionspuffers, wobei eine inkorrekte offsets_size dazu führt, dass Binder die Verarbeitung von Objekten in einer Transaktion überspringt, aber dennoch versucht, diese zu bereinigen, wodurch die Referenzzähler für unverarbeitete binder_node-Objekte verringert und diese vorzeitig freigegeben werden. Eine nicht vertrauenswürdige App könnte dies über manipulierte Binder-Transaktionen ausnutzen, um Dangling Pointer in binder_buffer-Strukturen zu erzeugen, was Heap-Leaks ermöglicht (z. B. das Lesen von ptr- und cookie-Feldern aus freigegebenen Nodes während binder_thread_read), das Aufheben von Verknüpfungen für beliebige Kernel-Speicherschreibvorgänge und letztendlich die Eskalation zu Root-Rechten auf Geräten mit GKI-Kerneln 5.4/5.10; Eine vollständige Exploit-Kette wurde vom Android Red Team von Google auf der OffensiveCon 2024 demonstriert und betraf vollständig gepatchte Geräte bis zur vollständigen Behebung im Android Security Bulletin vom Juli 2023 (der erste Patch im Februar war unvollständig, was zu CVE-2023-21255 führte).

Eine detailliertere Beschreibung finden Sie in den Referenzen.

CVE‑2022‑20421 (improper cleanup / UAF)

CVE-2022-20421 stellt eine weitere UAF-Sicherheitslücke in der fehlerhaften Bereinigungslogik von Binder dar, die sich auf die Handhabung der Referenzzählung von binder_node durch den Treiber während der Transaktionsverarbeitung und in Fehlerpfaden auswirkt. Die Ursache liegt in einer nicht korrekten Verringerung der Node-referenzen, wenn Transaktionen vorzeitig fehlschlagen, wodurch ein lokaler Angreifer Kernel-Objekte freigeben kann, während Pointer auf diese in Transaktionspuffern oder Thread-Zuständen weiterhin aktiv bleiben. Die Ausnutzung erfolgte durch das Senden fehlerhafter Binder-Transaktionen, um die UAF auszulösen, Kernel-Heap-Daten offenzulegen und beliebige Lese-/Schreibzugriffe für Sandbox-Escape und Privilegieneskalation zu erlangen; öffentliche Analysen bringen dies mit Mustern in der Objektlebensdauerverwaltung von Binder in Verbindung, wobei Patches im Android Security Bulletin vom November 2022 veröffentlicht wurden.

Eine detailliertere Beschreibung finden Sie in den Referenzen.

Dieser Artikel wurde verfasst von