Missbrauch von Android Media-Pipelines & Image-Parsern

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

Zustellung: Messaging-Apps ➜ MediaStore ➜ Privilegierte Parser

Moderne OEM-Builds führen regelmäßig privilegierte Media-Indexer aus, die MediaStore für “AI” oder Sharing-Funktionen erneut scannen. Auf Samsung-Firmware vor dem Patch vom April 2025 lädt com.samsung.ipservice Quram (/system/lib64/libimagecodec.quram.so) und parst automatisch jede Datei, die WhatsApp (oder andere Apps) in den MediaStore ablegt. In der Praxis kann ein Angreifer eine DNG senden, die als IMG-*.jpg getarnt ist, darauf warten, dass das Opfer auf “download” tippt (1-Klick), und der privilegierte Dienst parst die Nutzlast, selbst wenn das Opfer die Galerie nie öffnet.

$ file IMG-2025-02-10.jpeg
TIFF image data ...
$ exiftool IMG-2025-02-10.jpeg | grep "Opcode List"
Opcode List 1 : [opcode 23], [opcode 23], ...

Kernaussagen

  • Die Zustellung beruht auf dem erneuten Parsen durch das System-Media-Subsystem (nicht durch den Chat-Client) und erbt damit dessen Berechtigungen (voller Lese-/Schreibzugriff auf die Galerie, Möglichkeit, neue Medien abzulegen usw.).
  • Jeder Image-Parser, der über MediaStore erreichbar ist (vision widgets, wallpapers, AI résumé features usw.), wird remote zugänglich, wenn ein Angreifer ein Ziel dazu bringt, Medien zu speichern.

0-click DD+/EAC-3 decoding path (Google Messages ➜ mediacodec sandbox)

Moderne Messaging-Stacks dekodieren außerdem automatisch audio für Transkription/Suche. Auf dem Pixel 9 übergibt Google Messages eingehendes RCS/SMS-Audio an den Dolby Unified Decoder (UDC) in /vendor/lib64/libcodec2_soft_ddpdec.so bevor der Benutzer die Nachricht öffnet, wodurch die 0-click-Angriffsfläche auf Media-Codecs ausgeweitet wird.

Wesentliche Parse-Einschränkungen

  • Jeder DD+ syncframe hat bis zu 6 Blocks; jeder Block kann bis zu 0x1FF Bytes angreifer-kontrollierte skip data in einen Skip-Buffer kopieren (≈ 0x1FF * 6 Bytes pro Frame).
  • Der Skip-Buffer wird auf EMDF gescannt: syncword (0xX8) + emdf_container_length (16b) + variable-length fields. emdf_payload_size wird mit einer unbeschränkten variable_bits(8)-Schleife geparst.
  • EMDF-Payload-Bytes werden in einem per-Frame benutzerdefinierten “evo heap” bump-Allocator alloziert und dann byteweise aus einem Bit-Reader kopiert, der durch emdf_container_length begrenzt ist.

Integer-overflow → heap-overflow primitive (CVE-2025-54957)

  • ddp_udc_int_evo_malloc richtet alloc_size+extra auf 8 Bytes aus via total_size += (8 - total_size) % total_size ohne Wrap-Detection. Werte nahe 0xFFFFFFFFFFFFFFF9..FF schrumpfen auf AArch64 zu einem winzigen total_size.
  • Die Kopierschleife verwendet weiterhin die logische payload_length aus emdf_payload_size, sodass Angreifer-Bytes evo-heap-Daten über das zu kleine Chunk hinaus überschreiben.
  • Die Overflow-Länge wird präzise durch den Angreifer wählbaren emdf_container_length begrenzt; die Overflow-Bytes sind angreifer-kontrollierte EMDF-Payload-Daten. Der Slab-Allocator wird jedes Syncframe zurückgesetzt, was vorhersehbare Nachbarschaften erlaubt.

Sekundäres Lese-Primitive Wenn emdf_container_length > skipl, liest die EMDF-Parsing-Logik über initialisierte Skip-Bytes hinaus (OOB read). Alleinstehend leaked das nur Nullen/bekannte Mediendaten, aber nach der Korruption angrenzender Heap-Metadaten kann es die korrumpierte Region zurücklesen, um den Exploit zu validieren.

Ausnutzungsablauf

  1. EMDF so konstruieren, dass emdf_payload_size groß ist (via variable_bits(8)), sodass die Allocator-Padding-Operation wrappt und ein kleines Chunk entsteht.
  2. emdf_container_length auf die gewünschte Overflow-Länge setzen (≤ insgesamt verfügbares Skip-Data-Budget); Overflow-Bytes in der EMDF-Payload platzieren.
  3. Den per-Frame evo heap so formen, dass die kleine Allokation vor Zielstrukturen im statischen Decoder-Buffer (≈693 KB) oder im dynamischen Buffer (≈86 KB), der pro Decoder-Instanz einmalig alloziert wird, liegt.
  4. Optional emdf_container_length > skipl wählen, um nach der Korruption überschriebenen Daten aus dem Skip-Buffer zurückzulesen.

Quram’s DNG Opcode Interpreter Bugs

DNG-Dateien betten drei Opcode-Listen ein, die in verschiedenen Dekodierstufen angewendet werden. Quram kopiert Adobes API, aber sein Stage-3-Handler für DeltaPerColumn (opcode ID 11) vertraut angreifer-gesendeten plane-Bounds.

Fehlende Prüfungen der Plane-Bounds in DeltaPerColumn

  • Angreifer setzen plane=5125 und planes=5123, obwohl Stage-3-Bilder nur Plane 0–2 (RGB) exposen.
  • Quram berechnet opcode_last_plane = image_planes + opcode_planes anstelle von plane + count und prüft nie, ob der resultierende Plane-Bereich innerhalb des Bildes liegt.
  • Die Schleife schreibt daher ein Delta in raw_pixel_buffer[plane_index] mit einem vollständig kontrollierten Offset (z.B. plane 5125 ⇒ Offset 5125 * 2 bytes/pixel = 0x2800). Jeder Opcode addiert einen 16-Bit-Float-Wert (0x6666) an die Zielposition und liefert so ein präzises Heap-OOB-Add-Primitive.

Inkremente in beliebige Writes verwandeln

  • Der Exploit korrumpiert zuerst QuramDngImage.bottom/right in Stage-3 mittels 480 malformed DeltaPerColumn-Operationen, sodass nachfolgende Opcodes riesige Koordinaten als in-bounds betrachten.
  • MapTable-Opcodes (opcode 7) werden dann auf diese gefälschten Bounds gerichtet. Mit einer Substitutions-Tabelle aus Nullen oder einem DeltaPerColumn mit -Inf-Deltas nullt der Angreifer beliebige Regionen und wendet danach zusätzliche Deltas an, um exakte Werte zu schreiben.
  • Weil die Opcode-Parameter in den DNG-Metadaten leben, kann das Payload hunderttausende Writes codieren, ohne direkt Prozessspeicher zu modifizieren.

Heap-Shaping unter Scudo

Scudo bucketed Allokationen nach Größe. Quram allokiert zufällig die folgenden Objekte mit identischen 0x30-Byte-Chunks, sodass sie in derselben Region landen (0x40-Byte-Abstand auf dem Heap):

  • QuramDngImage-Deskriptoren für Stage 1/2/3
  • QuramDngOpcodeTrimBounds und vendor Unknown opcodes (ID ≥14, einschließlich ID 23)

Der Exploit sequenziert Allokationen, um deterministisch Chunks zu platzieren:

  1. Stage-1 Unknown(23)-Opcodes (20.000 Einträge) sprühen 0x30-Chunks, die später freed werden.
  2. Stage-2 freed diese Opcodes und platziert ein neues QuramDngImage in den freigegebenen Regionen.
  3. 240 Stage-2 Unknown(23)-Einträge werden freed, und Stage-3 allokiert sofort sein QuramDngImage plus einen neuen raw pixel buffer gleicher Größe und reused diese Plätze.
  4. Ein konstruiertes TrimBounds-Opcode läuft zuerst in Liste 3 und allokiert noch einen raw pixel buffer, bevor Stage-2-State freed wird, was “raw pixel buffer ➜ QuramDngImage”-Adjazenz garantiert.
  5. Zusätzlich 640 TrimBounds-Einträge werden mit minVersion=1.4.0.1 markiert, sodass der Dispatcher sie überspringt, ihre zugrundeliegenden Objekte aber alloziert bleiben und später primitive Ziele werden.

Diese Choreographie setzt den Stage-3-Raw-Buffer unmittelbar vor das Stage-3-QuramDngImage, sodass der plane-basierte Overflow Felder im Deskriptor umschreibt statt zufälligen State zum Absturz zu bringen.

Vendor-“Unknown”-Opcodes als Datenblöcke wiederverwenden

Samsung lässt das High-Bit in vendor-spezifischen Opcode-IDs gesetzt (z.B. ID 23), was den Interpreter anweist, die Struktur zu alloziieren, aber die Ausführung zu überspringen. Der Exploit missbraucht diese ruhenden Objekte als angreifer-kontrollierte Heaps:

  • Opcode-Liste 1 und 2 Unknown(23)-Einträge dienen als zusammenhängende Scratchpads zum Speichern von Payload-Bytes (JOP-Chain bei Offset 0xf000 und ein Shell-Command bei 0x10000 relativ zum raw buffer).
  • Weil der Interpreter jedes Objekt beim Verarbeiten von Liste 3 trotzdem als Opcode behandelt, reicht es später, die vtable eines Objekts zu kapern, um damit begonnene Angreifer-Ausführung zu starten.

Konstruiere gefälschte MapTable-Objekte & ASLR umgehen

MapTable-Objekte sind größer als TrimBounds, aber sobald die Layout-Korruption ankommt, liest der Parser gern zusätzliche Parameter out-of-bounds:

  1. Verwende das lineare Write-Primitive, um teilweise einen TrimBounds-vtable-Pointer mit einer gefälschten MapTable-Substitutionstabelle zu überschreiben, die die unteren 2 Bytes von einer benachbarten TrimBounds-vtable auf die MapTable-vtable mappt. Nur die Low-Bytes unterscheiden sich zwischen unterstützten Quram-Builds, sodass eine einzelne 64K Lookup-Tabelle sieben Firmware-Versionen und jeden 4 KB ASLR-Slide abdeckt.
  2. Patch den Rest der TrimBounds-Felder (top/left/width/planes), damit das Objekt später wie eine gültige MapTable reagiert.
  3. Führe das gefälschte Opcode über nullgesetzten Speicher aus. Weil der Substitutionstabelle-Pointer tatsächlich auf die vtable eines anderen Opcodes zeigt, werden die Output-Bytes zu leaked Low-Order-Adressen aus libimagecodec.quram.so oder dessen GOT.
  4. Wende zusätzliche MapTable-Durchläufe an, um jene Zwei-Byte-Leaks in Offsets zu Gadgets wie __ink_jpeg_enc_process_image+64, QURAMWINK_Read_IO2+124, qpng_check_IHDR+624 und libc’s __system_property_get-Entry zu konvertieren. Die Angreifer bauen so effektiv vollständige Adressen innerhalb ihrer gesprayten Opcode-Region wieder auf, ohne native Memory-Disclosure-APIs.

JOP ➜ system()-Übergang auslösen

Sobald Gadget-Pointer und Shell-Command im Opcode-Spray platziert sind:

  1. Eine finale Welle von DeltaPerColumn-Writes addiert 0x0100 zu Offset 0x22 des Stage-3-QuramDngImage, verschiebt dessen raw buffer Pointer um 0x10000, sodass er nun auf den Angreifer-Command-String zeigt.
  2. Der Interpreter beginnt, das Ende von 1040 Unknown(23)-Opcodes auszuführen. Der erste korrumpierte Eintrag hat seine vtable durch die gefälschte Tabelle bei Offset 0xf000 ersetzt, sodass QuramDngOpcode::aboutToApply qpng_read_data (den 4. Eintrag) aus der Fake-Tabelle resolved.
  3. Die verketteten Gadgets führen aus: lade den QuramDngImage-Pointer, addiere 0x20, um auf den raw buffer Pointer zu zeigen, dereferenziere ihn, kopiere das Ergebnis in x19/x0, und springe dann durch GOT-Slots, die auf system umgeschrieben wurden. Weil der raw buffer Pointer jetzt auf den Angreifer-String zeigt, führt das finale Gadget system(<shell command>) innerhalb von com.samsung.ipservice aus.

Hinweise zu Allocator-Varianten

Es existieren zwei Payload-Familien: eine für jemalloc abgestimmt, die andere für scudo. Sie unterscheiden sich darin, wie Opcode-Blöcke geordnet werden, um Adjazenz zu erreichen, teilen aber die gleichen logischen Primitives (DeltaPerColumn-Bug ➜ MapTable zero/write ➜ bogus vtable ➜ JOP). Scudos deaktivierte Quarantine macht die Wiederverwendung von 0x30-Byte-Freelists deterministisch, während jemalloc auf Size-Class-Kontrolle via tile/subIFD-Sizing angewiesen ist.

References

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks