Skip to content

Commit 05e43cc

Browse files
authored
Merge pull request #1688 from HackTricks-wiki/update_In-the-wild_Android_RCE_via_malicious_DNG_opcodes__20251217_023111
In-the-wild Android RCE via malicious DNG opcodes against Sa...
2 parents 230e691 + e4d8292 commit 05e43cc

File tree

4 files changed

+90
-0
lines changed

4 files changed

+90
-0
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@
351351

352352
- [Android APK Checklist](mobile-pentesting/android-checklist.md)
353353
- [Android Applications Pentesting](mobile-pentesting/android-app-pentesting/README.md)
354+
- [Abusing Android Media Pipelines Image Parsers](mobile-pentesting/android-app-pentesting/abusing-android-media-pipelines-image-parsers.md)
354355
- [Accessibility Services Abuse](mobile-pentesting/android-app-pentesting/accessibility-services-abuse.md)
355356
- [Android Anti Instrumentation And Ssl Pinning Bypass](mobile-pentesting/android-app-pentesting/android-anti-instrumentation-and-ssl-pinning-bypass.md)
356357
- [Android Applications Basics](mobile-pentesting/android-app-pentesting/android-applications-basics.md)

src/binary-exploitation/common-exploiting-problems.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,14 @@ int main(void) {
153153
154154
This workflow lets you triage anti-debug-protected JNI validators quickly, leak secrets when needed, then fuzz only the meaningful bytes, all without touching the original APK.
155155
156+
## Image/Media Parsing Exploits (DNG/TIFF/JPEG)
157+
158+
Malicious camera formats often ship their own bytecode (opcode lists, map tables, tone curves). When a privileged decoder fails to bound-check metadata-derived dimensions or plane indices, those opcodes become attacker-controlled read/write primitives that can groom the heap, pivot pointers, or even leak ASLR. Samsung's in-the-wild Quram exploit is a recent example of chaining a `DeltaPerColumn` bounds bug, heap spraying via skipped opcodes, vtable remapping, and a JOP chain to `system()`.
159+
160+
{{#ref}}
161+
../mobile-pentesting/android-app-pentesting/abusing-android-media-pipelines-image-parsers.md
162+
{{#endref}}
163+
156164
## Pointer-Keyed Hash Table Pointer Leaks on Apple Serialization
157165
158166
### Requirements & attack surface

src/mobile-pentesting/android-app-pentesting/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ android-enterprise-work-profile-bypass.md
7373
../../linux-hardening/privilege-escalation/android-rooting-frameworks-manager-auth-bypass-syscall-hook.md
7474
{{#endref}}
7575

76+
{{#ref}}
77+
abusing-android-media-pipelines-image-parsers.md
78+
{{#endref}}
7679

7780
{{#ref}}
7881
../../binary-exploitation/linux-kernel-exploitation/arm64-static-linear-map-kaslr-bypass.md
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Abusing Android Media Pipelines & Image Parsers
2+
3+
{{#include ../../banners/hacktricks-training.md}}
4+
5+
## Delivery: Messaging Apps ➜ MediaStore ➜ Privileged Parsers
6+
7+
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.
8+
9+
```bash
10+
$ file IMG-2025-02-10.jpeg
11+
TIFF image data ...
12+
$ exiftool IMG-2025-02-10.jpeg | grep "Opcode List"
13+
Opcode List 1 : [opcode 23], [opcode 23], ...
14+
```
15+
16+
**Key takeaways**
17+
- 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.).
18+
- 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.
19+
20+
## Quram's DNG Opcode Interpreter Bugs
21+
22+
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.
23+
24+
### Failing plane bounds in `DeltaPerColumn`
25+
- Attackers set `plane=5125` and `planes=5123` even though Stage-3 images only expose planes 0–2 (RGB).
26+
- Quram computes `opcode_last_plane = image_planes + opcode_planes` instead of `plane + count`, and never checks whether the resulting plane range fits inside the image.
27+
- The loop therefore writes a delta to `raw_pixel_buffer[plane_index]` with a fully controlled offset (e.g., plane 5125 ⇒ offset `5125 * 2 bytes/pixel = 0x2800`). Each opcode adds a 16-bit float value (0x6666) to the targeted location, yielding a precise heap OOB add primitive.
28+
29+
### Turning increments into arbitrary writes
30+
- The exploit first corrupts Stage-3 `QuramDngImage.bottom/right` using 480 malformed `DeltaPerColumn` operations so future opcodes treat enormous coordinates as in-bounds.
31+
- `MapTable` opcodes (opcode 7) are then aimed at those fake bounds. Using a substitution table of all zeros or a `DeltaPerColumn` with `-Inf` deltas, the attacker zeroes any region, then applies additional deltas to write exact values.
32+
- Because the opcode parameters live inside the DNG metadata, the payload can encode hundreds of thousands of writes without touching process memory directly.
33+
34+
## Heap Shaping Under Scudo
35+
36+
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):
37+
- `QuramDngImage` descriptors for Stage 1/2/3
38+
- `QuramDngOpcodeTrimBounds` and vendor `Unknown` opcodes (ID ≥14, including ID 23)
39+
40+
The exploit sequences allocations to deterministically place chunks:
41+
1. Stage-1 `Unknown(23)` opcodes (20,000 entries) spray 0x30 chunks that later get freed.
42+
2. Stage-2 frees those opcodes and places a new `QuramDngImage` inside the freed region.
43+
3. 240 Stage-2 `Unknown(23)` entries are freed, and Stage-3 immediately allocates its `QuramDngImage` plus a new raw pixel buffer of the same size, reusing those spots.
44+
4. A crafted `TrimBounds` opcode runs first in list 3 and allocates yet another raw pixel buffer before freeing Stage-2 state, guaranteeing "raw pixel buffer ➜ QuramDngImage" adjacency.
45+
5. 640 additional `TrimBounds` entries are marked `minVersion=1.4.0.1` so the dispatcher skips them, but their backing objects stay allocated and later become primitive targets.
46+
47+
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.
48+
49+
## Reusing Vendor "Unknown" Opcodes as Data Blobs
50+
51+
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:
52+
- 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).
53+
- 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.
54+
55+
## Crafting Bogus `MapTable` Objects & Bypassing ASLR
56+
57+
`MapTable` objects are larger than `TrimBounds`, but once the layout corruption lands, the parser happily reads extra parameters out-of-bounds:
58+
1. Use the linear write primitive to partially overwrite a `TrimBounds` vtable pointer with a crafted `MapTable` substitution table that maps lower 2 bytes from a neighbouring `TrimBounds` vtable to the `MapTable` vtable. 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.
59+
2. Patch the rest of the `TrimBounds` fields (top/left/width/planes) so the object behaves like a valid `MapTable` when executed later.
60+
3. 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.so` or its GOT.
61+
4. Apply additional `MapTable` passes 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_get` entry. The attackers effectively rebuild full addresses inside their sprayed opcode region without native memory disclosure APIs.
62+
63+
## Triggering the JOP ➜ `system()` Transition
64+
65+
Once the gadget pointers and shell command are staged inside the opcode spray:
66+
1. A final wave of `DeltaPerColumn` writes adds `0x0100` to offset 0x22 of the Stage-3 `QuramDngImage`, shifting its raw buffer pointer by 0x10000 so it now references the attacker command string.
67+
2. 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, so `QuramDngOpcode::aboutToApply` resolves `qpng_read_data` (the 4th entry) out of the fake table.
68+
3. The chained gadgets perform: load the `QuramDngImage` pointer, add 0x20 to point at the raw buffer pointer, dereference it, copy the result into `x19/x0`, then jump through GOT slots rewritten to `system`. Because the raw buffer pointer now equals the attacker string, the final gadget executes `system(<shell command>)` inside `com.samsung.ipservice`.
69+
70+
## Notes on Allocator Variants
71+
72+
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.
73+
74+
## References
75+
76+
- [Project Zero – A look at an Android ITW DNG exploit](https://projectzero.google/2025/12/android-itw-dng.html)
77+
78+
{{#include ../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)