Abusing Android Media Pipelines & Image Parsers
Tip
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the đŹ Discord group or the telegram group or follow us on Twitter đŠ @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
Delivery: Messaging Apps â MediaStore â Privileged Parsers
Modern OEM builds regularly run privileged media indexers that rescan MediaStore for âAIâ or sharing features. On Samsung firmware prior to the April 2025 patch, com.samsung.ipservice loads Quram (/system/lib64/libimagecodec.quram.so) and automatically parses any file WhatsApp (or other apps) drops into MediaStore. In practice an attacker can send a DNG disguised as IMG-*.jpg, wait for the victim to tap âdownloadâ (1-click), and the privileged service will parse the payload even if the user never opens the gallery.
$ 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], ...
Key takeaways
- Delivery relies on system media re-parsing (not the chat client) and thus inherits that processâ permissions (full read/write access to the gallery, ability to drop new media, etc.).
- Any image parser reachable through
MediaStore(vision widgets, wallpapers, AI résumé features, etc.) becomes remotely reachable if the attacker can convince a target to save media.
Quramâs DNG Opcode Interpreter Bugs
DNG files embed three opcode lists applied at different decode stages. Quram copies Adobeâs API, but its Stage-3 handler for DeltaPerColumn (opcode ID 11) trusts attacker-supplied plane bounds.
Failing plane bounds in DeltaPerColumn
- Attackers set
plane=5125andplanes=5123even though Stage-3 images only expose planes 0â2 (RGB). - Quram computes
opcode_last_plane = image_planes + opcode_planesinstead ofplane + count, and never checks whether the resulting plane range fits inside the image. - The loop therefore writes a delta to
raw_pixel_buffer[plane_index]with a fully controlled offset (e.g., plane 5125 â offset5125 * 2 bytes/pixel = 0x2800). Each opcode adds a 16-bit float value (0x6666) to the targeted location, yielding a precise heap OOB add primitive.
Turning increments into arbitrary writes
- The exploit first corrupts Stage-3
QuramDngImage.bottom/rightusing 480 malformedDeltaPerColumnoperations so future opcodes treat enormous coordinates as in-bounds. MapTableopcodes (opcode 7) are then aimed at those fake bounds. Using a substitution table of all zeros or aDeltaPerColumnwith-Infdeltas, the attacker zeroes any region, then applies additional deltas to write exact values.- Because the opcode parameters live inside the DNG metadata, the payload can encode hundreds of thousands of writes without touching process memory directly.
Heap Shaping Under Scudo
Scudo buckets allocations by size. Quram happens to allocate the following objects with identical 0x30-byte chunk sizes, so they land in the same region (0x40-byte spacing on the heap):
QuramDngImagedescriptors for Stage 1/2/3QuramDngOpcodeTrimBoundsand vendorUnknownopcodes (ID â„14, including ID 23)
The exploit sequences allocations to deterministically place chunks:
- 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.
This choreography puts the Stage-3 raw buffer immediately before the Stage-3 QuramDngImage, so the plane-based overflow flips fields inside the descriptor rather than crashing random state.
Reusing Vendor âUnknownâ Opcodes as Data Blobs
Samsung leaves the high bit set in vendor-specific opcode IDs (e.g., ID 23), which instructs the interpreter to allocate the structure but skip execution. The exploit abuses those dormant objects as attacker-controlled heaps:
- 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
Tip
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the đŹ Discord group or the telegram group or follow us on Twitter đŠ @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
HackTricks

