Abuser les pipelines multimédia Android et les 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
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Livraison : Applications de messagerie ➜ MediaStore ➜ Parseurs privilégiés
Les builds OEM modernes lancent régulièrement des indexeurs multimédia privilégiés qui parcourent à nouveau MediaStore pour des fonctionnalités “AI” 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) dépose 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 la charge utile 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 repose sur le re-parsing média du système (pas le client de chat) et hérite donc des permissions de ce processus (accès lecture/écriture complet à la galerie, capacité à déposer de nouveaux médias, etc.).
- Tout image parser accessible via
MediaStore(vision widgets, wallpapers, fonctionnalités AI de résumé, etc.) devient accessible à distance si l’attaquant parvient à convaincre une cible d’enregistrer un média.
0-click DD+/EAC-3 decoding path (Google Messages ➜ mediacodec sandbox)
Les stacks de messagerie modernes décodent aussi automatiquement l’audio pour la transcription/la recherche. Sur Pixel 9, Google Messages remet l’audio incoming RCS/SMS au Dolby Unified Decoder (UDC) situé dans /vendor/lib64/libcodec2_soft_ddpdec.so avant que l’utilisateur n’ouvre le message, étendant la surface 0-click aux media codecs.
Contraintes clés de parsing
- Chaque DD+ syncframe contient jusqu’à 6 blocks ; chaque block peut copier jusqu’à
0x1FFoctets de skip data contrôlée par l’attaquant dans un skip buffer (≈0x1FF * 6octets par frame). - Le skip buffer est scanné pour EMDF :
syncword (0xX8)+emdf_container_length(16b) + champs de longueur variable.emdf_payload_sizeest parsé avec une boucle non bornéevariable_bits(8). - Les octets de payload EMDF sont alloués dans un bump allocator per-frame personnalisé appelé “evo heap” puis copiés octet par octet depuis un bit-reader borné par
emdf_container_length.
Integer-overflow → heap-overflow primitive (CVE-2025-54957)
ddp_udc_int_evo_mallocalignealloc_size+extraà 8 octets viatotal_size += (8 - total_size) % total_sizesans détection de wrap. Des valeurs proches de0xFFFFFFFFFFFFFFF9..FFse réduisent à untotal_sizeminuscule sur AArch64.- La boucle de copie utilise toujours la
payload_lengthlogique issue deemdf_payload_size, donc les octets de l’attaquant écrasent des données de l’evo-heap au-delà du chunk sous-dimensionné. - La longueur d’overflow est précisément plafonnée par
emdf_container_lengthchoisi par l’attaquant ; les octets d’overflow sont des données EMDF contrôlées par l’attaquant. Le slab allocator est réinitialisé à chaque syncframe, donnant une adjacency prédictible.
Primitive de lecture secondaire
Si emdf_container_length > skipl, le parsing EMDF lit au-delà des skip bytes initialisés (OOB read). Pris seul, it leaks zeros/known media, mais après avoir corrompu des métadonnées heap adjacentes il peut relire la région corrompue pour valider l’exploit.
Recette d’exploitation
- Construire un EMDF avec un
emdf_payload_sizeénorme (viavariable_bits(8)) de sorte que le padding de l’allocateur wrappe en un petit chunk. - Définir
emdf_container_lengthà la longueur d’overflow désirée (≤ budget total de skip data) ; placer les octets d’overflow dans le payload EMDF. - Modeler l’evo heap per-frame pour que la petite allocation soit placée avant les structures cibles à l’intérieur du buffer statique du decoder (≈693 KB) ou du buffer dynamique (≈86 KB) alloué une fois par instance de decoder.
- Optionnellement choisir
emdf_container_length > skiplpour relire les données écrasées depuis le skip buffer après corruption.
Quram’s DNG Opcode Interpreter Bugs
Les fichiers DNG incorporent trois listes d’opcodes appliquées à différentes étapes du décodage. Quram reproduit l’API d’Adobe, mais son handler Stage-3 pour DeltaPerColumn (opcode ID 11) fait confiance aux bounds de plane fournis par l’attaquant.
Bounds de plane défaillants dans DeltaPerColumn
- Les attaquants positionnent
plane=5125etplanes=5123alors que les images Stage-3 n’exposent que les planes 0–2 (RGB). - Quram calcule
opcode_last_plane = image_planes + opcode_planesau lieu deplane + count, et ne vérifie jamais si la plage résultante de planes tient dans l’image. - La boucle écrit donc un delta dans
raw_pixel_buffer[plane_index]avec un offset totalement contrôlé (par ex. plane 5125 ⇒ offset5125 * 2 bytes/pixel = 0x2800). Chaque opcode ajoute une valeur float 16 bits (0x6666) à l’emplacement ciblé, produisant une primitive d’add heap OOB précise.
Transformer des incréments en écritures arbitraires
- L’exploit corrompt d’abord
QuramDngImage.bottom/rightdu Stage-3 en utilisant 480 opérationsDeltaPerColumnmalformées afin que les opcodes futurs considèrent d’énormes coordonnées comme in-bounds. - Les opcodes
MapTable(opcode 7) sont alors dirigés vers ces bounds factices. En utilisant une table de substitution composée de zéros complets ou unDeltaPerColumnavec des deltas-Inf, l’attaquant met à zéro n’importe quelle région, puis applique des deltas additionnels pour écrire des valeurs exactes. - Puisque les paramètres des opcodes vivent dans les metadata DNG, le payload peut encoder des centaines de milliers d’écritures sans toucher directement la mémoire du processus.
Heap Shaping Under Scudo
Scudo bucketise les allocations par taille. Quram alloue par hasard les objets suivants avec des tailles de chunk identiques de 0x30 bytes, ils atterrissent donc dans la même région (espacement 0x40 bytes sur le heap) :
- des descriptors
QuramDngImagepour Stage 1/2/3 QuramDngOpcodeTrimBoundset opcodes vendorUnknown(ID ≥14, incluant ID 23)
L’exploit ordonne les allocations pour placer les chunks de façon déterministe :
- Les opcodes Stage-1
Unknown(23)(20 000 entrées) sprayent des chunks 0x30 qui seront plus tard freed. - Stage-2 libère ces opcodes et place un nouveau
QuramDngImagedans la région libérée. - 240 entrées Stage-2
Unknown(23)sont freed, et le Stage-3 alloue immédiatement sonQuramDngImageplus un nouveau raw pixel buffer de la même taille, réutilisant ces emplacements. - Un
TrimBoundscrafté 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”. - 640
TrimBoundssupplémentaires sont marquésminVersion=1.4.0.1pour que le dispatcher les saute, mais leurs objets backing restent alloués et deviennent plus tard des cibles primitives.
Cette chorégraphie place le raw buffer du Stage-3 immédiatement avant le QuramDngImage Stage-3, si bien que l’overflow basé sur les planes inverse des champs à l’intérieur du descriptor au lieu de crasher de l’état aléatoire.
Reuse des opcodes vendor “Unknown” comme blobs de données
Samsung laisse le bit haute positionné dans les IDs d’opcodes vendor (par ex. ID 23), ce qui ordonne à l’interpréteur d’allouer la structure mais de sauter son 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 (JOP chain à offset 0xf000 et une commande shell à 0x10000 relative au raw buffer). - Parce que l’interpréteur traite toujours chaque objet comme un opcode quand la liste 3 est processée, prendre le contrôle de la vtable d’un objet suffit plus tard à commencer l’exécution des données de l’attaquant.
Construction de faux objets MapTable & contournement de l’ASLR
Les objets MapTable sont plus grands que TrimBounds, mais une fois la corruption de layout effectuée, le parser lit volontiers des paramètres supplémentaires hors-bornes :
- Utiliser la primitive d’écriture linéaire pour écraser partiellement un pointeur de vtable
TrimBoundsavec une table de substitutionMapTablecraftée qui mappe les 2 bytes bas d’une vtableTrimBoundsvoisine vers la vtableMapTable. Seuls les octets bas diffèrent entre les builds Quram supportés, donc une unique table de lookup 64K peut couvrir sept versions firmware et chaque slide ASLR de 4 KB. - Patcher le reste des champs
TrimBounds(top/left/width/planes) afin que l’objet se comporte comme unMapTablevalide quand il sera exécuté plus tard. - Exécuter l’opcode factice sur une mémoire nulle. Parce que le pointeur de table de substitution référence en réalité la vtable d’un autre opcode, les octets de sortie deviennent des low-order addresses leaked depuis
libimagecodec.quram.soou son GOT. - Appliquer des passes
MapTableadditionnelles pour convertir ces fuites de deux 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_getde libc. Les attaquants reconstruisent ainsi des adresses complètes à l’intérieur de leur région sprayée d’opcodes sans API native de divulgation mémoire.
Déclencher la transition JOP ➜ system()
Une fois les pointeurs de gadget et la commande shell placés dans le spray d’opcodes :
- Une dernière vague d’écritures
DeltaPerColumnajoute0x0100à l’offset 0x22 duQuramDngImageStage-3, décalant son pointeur de raw buffer de 0x10000 de sorte qu’il référence maintenant la chaîne de commande de l’attaquant. - L’interpréteur commence à exécuter la queue de 1040 opcodes
Unknown(23). La première entrée corrompue a sa vtable remplacée par la table forgée à l’offset 0xf000, doncQuramDngOpcode::aboutToApplyrésoutqpng_read_data(la 4ᵉ entrée) depuis la fausse table. - Les gadgets enchaînés effectuent : chargement du pointeur
QuramDngImage, addition de 0x20 pour pointer vers le pointeur raw buffer, déréférencement, copie du résultat dansx19/x0, puis saut via des slots GOT réécrits verssystem. Parce que le pointeur raw buffer vaut maintenant la chaîne de l’attaquant, le gadget final exécutesystem(<shell command>)à l’intérieur decom.samsung.ipservice.
Notes 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 blocks d’opcode pour obtenir l’adjacence mais partagent les mêmes primitives logiques (bug DeltaPerColumn ➜ MapTable zero/write ➜ fake vtable ➜ JOP). La quarantaine désactivée de Scudo rend la réutilisation du freelist 0x30-byte déterministe, tandis que jemalloc s’appuie sur le contrôle des size-class via le tile/subIFD sizing.
Références
- Project Zero – A look at an Android ITW DNG exploit
- Project Zero – Pixel 0-click: CVE-2025-54957 in Dolby UDC
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
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.


