Abuser des pipelines médias Android & des parseurs d’images

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Delivery: Messaging Apps ➜ MediaStore ➜ Privileged Parsers

Les builds OEM modernes exécutent régulièrement des indexeurs média privilégiés qui rescannent MediaStore pour des fonctionnalités d’IA ou de partage. Sur les firmwares Samsung antérieurs au patch d’avril 2025, com.samsung.ipservice charge Quram (/system/lib64/libimagecodec.quram.so) et analyse automatiquement tout fichier que WhatsApp (ou d’autres apps) place dans MediaStore. En pratique, un attaquant peut envoyer un DNG déguisé en IMG-*.jpg, attendre que la victime appuie sur “download” (1-clic), et le service privilégié analysera le payload même si l’utilisateur n’ouvre jamais la galerie.

$ 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], ...

Points clés

  • La livraison dépend de la réanalyse média du système (pas du client de chat) et hérite donc des permissions de ce processus (accès complet lecture/écriture à la galerie, capacité à déposer de nouveaux médias, etc.).
  • Tout parseur d’images accessible via MediaStore (vision widgets, fonds d’écran, fonctionnalités AI résumé, etc.) devient accessible à distance si l’attaquant parvient à convaincre la cible d’enregistrer un média.

Bugs de l’interpréteur d’opcodes DNG de Quram

Les fichiers DNG intègrent trois listes d’opcodes appliquées à différents stades de décodage. Quram recopie l’API d’Adobe, mais son gestionnaire Stage-3 pour DeltaPerColumn (opcode ID 11) fait confiance aux limites de plans fournies par l’attaquant.

Bornes de plan non valides dans DeltaPerColumn

  • Les attaquants définissent plane=5125 et planes=5123 alors que les images Stage-3 n’exposent que les plans 0–2 (RGB).
  • Quram calcule opcode_last_plane = image_planes + opcode_planes au lieu de plane + count, et ne vérifie jamais si la plage de plans résultante tient dans l’image.
  • La boucle écrit donc un delta dans raw_pixel_buffer[plane_index] avec un offset entièrement contrôlé (par ex. plane 5125 ⇒ offset 5125 * 2 bytes/pixel = 0x2800). Chaque opcode ajoute une valeur float 16-bit (0x6666) à l’emplacement ciblé, fournissant une primitive précise d’addition OOB sur le heap.

Transformer des incréments en écritures arbitraires

  • L’exploit corrompt d’abord QuramDngImage.bottom/right de Stage-3 en utilisant 480 opérations DeltaPerColumn malformées pour que les opcodes suivants traitent des coordonnées énormes comme étant in-bounds.
  • Des opcodes MapTable (opcode 7) sont ensuite dirigés vers ces limites falsifiées. En utilisant une table de substitution composée de zéros complets ou un DeltaPerColumn avec des deltas -Inf, l’attaquant met à zéro n’importe quelle région, puis applique des deltas supplémentaires pour écrire des valeurs exactes.
  • Parce que les paramètres d’opcode résident dans les métadonnées DNG, le payload peut encoder des centaines de milliers d’écritures sans toucher directement la mémoire du processus.

Modelage du heap sous Scudo

Scudo regroupe les allocations par taille. Quram alloue par hasard les objets suivants avec des chunks de 0x30 bytes identiques, ils atterrissent donc dans la même région (espacement de 0x40 bytes sur le heap) :

  • QuramDngImage descriptors for Stage 1/2/3
  • QuramDngOpcodeTrimBounds and vendor Unknown opcodes (ID ≥14, including ID 23)

L’exploit orchestre les allocations pour placer les chunks de façon déterministe :

  1. Les opcodes Stage-1 Unknown(23) (20 000 entrées) pulvérisent des chunks 0x30 qui seront plus tard freed.
  2. Stage-2 libère ces opcodes et place un nouveau QuramDngImage dans la région libérée.
  3. 240 entrées Stage-2 Unknown(23) sont freed, et Stage-3 alloue immédiatement son QuramDngImage plus un nouveau raw pixel buffer de la même taille, réutilisant ces emplacements.
  4. Un TrimBounds construit s’exécute en premier dans la liste 3 et alloue encore un raw pixel buffer avant de free l’état Stage-2, garantissant l’adjacence “raw pixel buffer ➜ QuramDngImage”.
  5. 640 entrées supplémentaires TrimBounds sont marquées minVersion=1.4.0.1 de sorte que le dispatcher les ignore, mais leurs objets sous-jacents restent alloués et deviennent plus tard des cibles primitives.

Cette chorégraphie place le buffer brut de Stage-3 immédiatement avant le QuramDngImage de Stage-3, de sorte que l’overflow basé sur les plans inverse des champs à l’intérieur du descripteur au lieu de crasher un état aléatoire.

Réutilisation des opcodes vendor “Unknown” comme blobs de données

Samsung laisse le bit de poids fort activé dans les IDs d’opcodes vendor-specific (par ex. ID 23), ce qui ordonne à l’interpréteur d’allouer la structure mais d’en sauter l’exécution. L’exploit abuse de ces objets dormants comme heaps contrôlés par l’attaquant :

  • Les entrées Unknown(23) des listes d’opcodes 1 et 2 servent de scratchpads contigus pour stocker des octets de payload (chaîne JOP à offset 0xf000 et une commande shell à 0x10000 relative au raw buffer).
  • Parce que l’interpréteur traite toujours chaque objet comme un opcode lorsqu’il parcourt la liste 3, s’emparer du vtable d’un objet suffit ensuite pour commencer à exécuter des données contrôlées.

Création d’objets MapTable falsifiés & contournement de l’ASLR

Les objets MapTable sont plus grands que TrimBounds, mais une fois la corruption de layout appliquée, le parser lit volontiers des paramètres supplémentaires hors-bounds :

  1. Utiliser la primitive d’écriture linéaire pour écraser partiellement un pointeur de vtable de TrimBounds par une table de substitution MapTable forgée qui mappe les 2 octets bas depuis le vtable TrimBounds voisin vers le vtable MapTable. Seuls les octets bas diffèrent entre les builds Quram supportés, donc une seule table de lookup 64K peut couvrir sept versions de firmware et chaque slide ASLR de 4 KB.
  2. Patch le reste des champs TrimBounds (top/left/width/planes) pour que l’objet se comporte comme un MapTable valide quand il sera exécuté.
  3. Exécuter l’opcode falsifié sur une mémoire mise à zéro. Parce que le pointeur de table de substitution référence en réalité le vtable d’un autre opcode, les octets de sortie deviennent des adresses bas-ordre leaked depuis libimagecodec.quram.so ou son GOT.
  4. Appliquer des passes MapTable supplémentaires pour convertir ces fuites de 2 octets en offsets vers des gadgets tels que __ink_jpeg_enc_process_image+64, QURAMWINK_Read_IO2+124, qpng_check_IHDR+624, et l’entrée __system_property_get de libc. Les attaquants reconstruisent ainsi des adresses complètes à l’intérieur de leur région d’opcode pulvérisée sans API de disclosure native.

Déclencher la transition JOP ➜ system()

Une fois les pointeurs de gadgets et la commande shell placés dans le spray d’opcodes :

  1. Une dernière vague d’écritures DeltaPerColumn ajoute 0x0100 à l’offset 0x22 du QuramDngImage de Stage-3, décalant son pointeur de raw buffer de 0x10000 pour qu’il référence désormais la chaîne de commande de l’attaquant.
  2. L’interpréteur commence à exécuter la queue de 1040 opcodes Unknown(23). La première entrée corrompue a son vtable remplacé par la table forgée à l’offset 0xf000, si bien que QuramDngOpcode::aboutToApply résout qpng_read_data (la 4ème entrée) à partir du fake table.
  3. Les gadgets chaînés effectuent : charger le pointeur QuramDngImage, ajouter 0x20 pour pointer sur le pointeur du raw buffer, le déréférencer, copier le résultat dans x19/x0, puis sauter via des slots GOT réécrits vers system. Comme le pointeur du raw buffer vaut désormais la chaîne de l’attaquant, le gadget final exécute system(<shell command>) à l’intérieur de com.samsung.ipservice.

Remarques sur les variantes d’allocateur

Deux familles de payload existent : une ajustée pour jemalloc, l’autre pour scudo. Elles diffèrent dans l’ordre des blocs d’opcodes pour obtenir l’adjacence mais partagent les mêmes primitives logiques (bug DeltaPerColumn ➜ MapTable zero/write ➜ vtable falsifié ➜ JOP). La quarantine désactivée de Scudo rend la réutilisation de freelist 0x30-byte déterministe, tandis que jemalloc s’appuie sur le contrôle de classes de taille via le tile/subIFD sizing.

Références

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks