Abuso de los flujos multimedia y analizadores de imágenes de Android
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Entrega: Apps de mensajería ➜ MediaStore ➜ Analizadores privilegiados
Las compilaciones OEM modernas ejecutan regularmente indexadores de medios privilegiados que vuelven a escanear MediaStore para funciones de “AI” o de compartir. En el firmware de Samsung anterior al parche de abril de 2025, com.samsung.ipservice carga Quram (/system/lib64/libimagecodec.quram.so) y analiza automáticamente cualquier archivo que WhatsApp (u otras apps) coloque en MediaStore. En la práctica, un atacante puede enviar un DNG disfrazado como IMG-*.jpg, esperar a que la víctima toque “download” (1 clic), y el servicio privilegiado analizará la carga útil incluso si el usuario nunca abre la galería.
$ 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], ...
Conclusiones clave
- La entrega depende del re-procesamiento de medios del sistema (no del cliente de chat) y por tanto hereda los permisos de ese proceso (acceso completo de lectura/escritura a la galería, capacidad para añadir nuevos medios, etc.).
- Cualquier analizador de imágenes accesible a través de
MediaStore(vision widgets, wallpapers, funciones de resumen AI, etc.) se vuelve accesible de forma remota si el atacante logra convencer a la víctima de guardar medios.
0-click DD+/EAC-3 decoding path (Google Messages ➜ mediacodec sandbox)
Las pilas de mensajería modernas también decodifican automáticamente audio para transcripción/búsqueda. En Pixel 9, Google Messages entregará el audio entrante de RCS/SMS al Dolby Unified Decoder (UDC) dentro de /vendor/lib64/libcodec2_soft_ddpdec.so antes de que el usuario abra el mensaje, ampliando la superficie 0-click a los codecs de medios.
Key parse constraints
- Each DD+ syncframe has up to 6 blocks; each block can copy up to
0x1FFbytes of attacker-controlled skip data into a skip buffer (≈0x1FF * 6bytes per frame). - The skip buffer is scanned for EMDF:
syncword (0xX8)+emdf_container_length(16b) + variable-length fields.emdf_payload_sizeis parsed with an unboundedvariable_bits(8)loop. - EMDF payload bytes are allocated inside a custom per-frame “evo heap” bump allocator and then copied byte-by-byte from a bit-reader bounded by
emdf_container_length.
Integer-overflow → heap-overflow primitive (CVE-2025-54957)
ddp_udc_int_evo_mallocalignsalloc_size+extrato 8 bytes viatotal_size += (8 - total_size) % total_sizewithout wrap detection. Values near0xFFFFFFFFFFFFFFF9..FFshrink to tinytotal_sizeon AArch64.- The copy loop still uses the logical
payload_lengthfromemdf_payload_size, so attacker bytes overwrite evo-heap data past the undersized chunk. - Overflow length is precisely capped by attacker-chosen
emdf_container_length; overflow bytes are attacker-controlled EMDF payload data. The slab allocator is reset every syncframe, giving predictable adjacency.
Secondary read primitive
Si emdf_container_length > skipl, EMDF parsing reads past initialized skip bytes (OOB read). Por sí solo leaks ceros/medios conocidos, pero tras corromper la metadata adyacente del heap puede leer de vuelta la región corrompida para validar el exploit.
Exploitation recipe
- Craft EMDF with huge
emdf_payload_size(viavariable_bits(8)) so allocator padding wraps into a small chunk. - Set
emdf_container_lengthto the desired overflow length (≤ total skip data budget); place overflow bytes in the EMDF payload. - Shape the per-frame evo heap so the small allocation sits before target structures inside the decoder’s static buffer (≈693 KB) or dynamic buffer (≈86 KB) allocated once per decoder instance.
- Optionally choose
emdf_container_length > skiplto read back overwritten data from the skip buffer after corruption.
Quram’s DNG Opcode Interpreter Bugs
Los archivos DNG incluyen tres listas de opcodes aplicadas en diferentes etapas de decodificación. Quram copia la API de Adobe, pero su manejador de Stage-3 para DeltaPerColumn (opcode ID 11) confía en límites de plano suministrados por el atacante.
Failing plane bounds in DeltaPerColumn
- Los atacantes fijan
plane=5125yplanes=5123aunque las imágenes Stage-3 solo exponen planos 0–2 (RGB). - Quram calcula
opcode_last_plane = image_planes + opcode_planesen lugar deplane + count, y nunca verifica si el rango de planos resultante cabe dentro de la imagen. - El bucle por tanto escribe un delta en
raw_pixel_buffer[plane_index]con un offset totalmente controlado (p. ej., plane 5125 ⇒ offset5125 * 2 bytes/pixel = 0x2800). Cada opcode suma un valor float de 16 bits (0x6666) a la ubicación objetivo, produciendo una primitiva precisa de suma OOB en el heap.
Turning increments into arbitrary writes
- El exploit primero corrompe Stage-3
QuramDngImage.bottom/rightusando 480 operaciones malformadasDeltaPerColumnpara que opcodes futuros traten coordenadas enormes como válidas (in-bounds). MapTableopcodes (opcode 7) se dirigen luego a esos límites falsos. Usando una tabla de sustitución de ceros o unDeltaPerColumncon deltas-Inf, el atacante pone a cero cualquier región y luego aplica deltas adicionales para escribir valores exactos.- Como los parámetros del opcode viven dentro de los metadatos DNG, el payload puede codificar cientos de miles de escrituras sin tocar la memoria del proceso directamente.
Heap Shaping Under Scudo
Scudo agrupa las asignaciones por tamaño. Quram sucede que asigna los siguientes objetos con tamaños de chunk idénticos de 0x30 bytes, por lo que caen en la misma región (espaciado de 0x40 bytes en el heap):
QuramDngImagedescriptors for Stage 1/2/3QuramDngOpcodeTrimBoundsand vendorUnknownopcodes (ID ≥14, including ID 23)
El exploit ordena las asignaciones para colocar los chunks de forma determinista:
- Stage-1
Unknown(23)opcodes (20,000 entries) spray 0x30 chunks that later get freed. - Stage-2 frees those opcodes and places a new
QuramDngImageinside the freed region. - 240 Stage-2
Unknown(23)entries are freed, and Stage-3 immediately allocates itsQuramDngImageplus a new raw pixel buffer of the same size, reusing those spots. - A crafted
TrimBoundsopcode runs first in list 3 and allocates yet another raw pixel buffer before freeing Stage-2 state, guaranteeing “raw pixel buffer ➜ QuramDngImage” adjacency. - 640 additional
TrimBoundsentries are markedminVersion=1.4.0.1so the dispatcher skips them, but their backing objects stay allocated and later become primitive targets.
Esta coreografía coloca el raw buffer de Stage-3 inmediatamente antes del QuramDngImage de Stage-3, de modo que el overflow basado en planos voltea campos dentro del descriptor en lugar de provocar un crash aleatorio del estado.
Reusing Vendor “Unknown” Opcodes as Data Blobs
Samsung deja el bit alto establecido en los IDs de opcode específicos del vendor (p. ej., ID 23), lo cual instruye al intérprete para asignar la estructura pero omitir su ejecución. El exploit abusa de esos objetos inactivos como heaps controlados por el atacante:
- Opcode list 1 and 2
Unknown(23)entries serve as contiguous scratchpads for storing payload bytes (JOP chain at offset 0xf000 and a shell command at 0x10000 relative to the raw buffer). - Because the interpreter still treats each object as an opcode when list 3 is processed, commandeering one object’s vtable later is enough to start executing attacker data.
Crafting Bogus MapTable Objects & Bypassing ASLR
MapTable objects are larger than TrimBounds, but once the layout corruption lands, the parser happily reads extra parameters out-of-bounds:
- Use the linear write primitive to partially overwrite a
TrimBoundsvtable pointer with a craftedMapTablesubstitution table that maps lower 2 bytes from a neighbouringTrimBoundsvtable to theMapTablevtable. Only the low bytes differ between supported Quram builds, so a single 64K lookup table can handle seven firmware versions and every 4 KB ASLR slide. - Patch the rest of the
TrimBoundsfields (top/left/width/planes) so the object behaves like a validMapTablewhen executed later. - Execute the fake opcode over zeroed memory. Because the substitution table pointer actually references another opcode’s vtable, the output bytes become leaked low-order addresses from
libimagecodec.quram.soor its GOT. - Apply additional
MapTablepasses to convert those two-byte leaks into offsets toward gadgets such as__ink_jpeg_enc_process_image+64,QURAMWINK_Read_IO2+124,qpng_check_IHDR+624, and libc’s__system_property_getentry. The attackers effectively rebuild full addresses inside their sprayed opcode region without native memory disclosure APIs.
Triggering the JOP ➜ system() Transition
Once the gadget pointers and shell command are staged inside the opcode spray:
- A final wave of
DeltaPerColumnwrites adds0x0100to offset 0x22 of the Stage-3QuramDngImage, shifting its raw buffer pointer by 0x10000 so it now references the attacker command string. - The interpreter starts executing the tail of 1040
Unknown(23)opcodes. The first corrupted entry has its vtable replaced with the forged table at offset 0xf000, soQuramDngOpcode::aboutToApplyresolvesqpng_read_data(the 4th entry) out of the fake table. - The chained gadgets perform: load the
QuramDngImagepointer, add 0x20 to point at the raw buffer pointer, dereference it, copy the result intox19/x0, then jump through GOT slots rewritten tosystem. Because the raw buffer pointer now equals the attacker string, the final gadget executessystem(<shell command>)insidecom.samsung.ipservice.
Notes on Allocator Variants
Two payload families exist: one tuned for jemalloc, another for scudo. They differ in how opcode blocks are ordered to achieve adjacency but share the same logical primitives (DeltaPerColumn bug ➜ MapTable zero/write ➜ bogus vtable ➜ JOP). Scudo’s disabled quarantine makes 0x30-byte freelist reuse deterministic, while jemalloc relies on size-class control via tile/subIFD sizing.
References
- Project Zero – A look at an Android ITW DNG exploit
- Project Zero – Pixel 0-click: CVE-2025-54957 in Dolby UDC
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.


