diff --git a/src/SUMMARY.md b/src/SUMMARY.md index e87c30665fe..b534f307e96 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -351,6 +351,7 @@ - [Android APK Checklist](mobile-pentesting/android-checklist.md) - [Android Applications Pentesting](mobile-pentesting/android-app-pentesting/README.md) + - [Abusing Android Media Pipelines Image Parsers](mobile-pentesting/android-app-pentesting/abusing-android-media-pipelines-image-parsers.md) - [Accessibility Services Abuse](mobile-pentesting/android-app-pentesting/accessibility-services-abuse.md) - [Android Anti Instrumentation And Ssl Pinning Bypass](mobile-pentesting/android-app-pentesting/android-anti-instrumentation-and-ssl-pinning-bypass.md) - [Android Applications Basics](mobile-pentesting/android-app-pentesting/android-applications-basics.md) diff --git a/src/binary-exploitation/common-exploiting-problems.md b/src/binary-exploitation/common-exploiting-problems.md index 48a96d5e709..dcb9db55bea 100644 --- a/src/binary-exploitation/common-exploiting-problems.md +++ b/src/binary-exploitation/common-exploiting-problems.md @@ -153,6 +153,14 @@ int main(void) { 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. +## Image/Media Parsing Exploits (DNG/TIFF/JPEG) + +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()`. + +{{#ref}} +../mobile-pentesting/android-app-pentesting/abusing-android-media-pipelines-image-parsers.md +{{#endref}} + ## Pointer-Keyed Hash Table Pointer Leaks on Apple Serialization ### Requirements & attack surface diff --git a/src/mobile-pentesting/android-app-pentesting/README.md b/src/mobile-pentesting/android-app-pentesting/README.md index 8c23a707e7b..f05da491487 100644 --- a/src/mobile-pentesting/android-app-pentesting/README.md +++ b/src/mobile-pentesting/android-app-pentesting/README.md @@ -73,6 +73,9 @@ android-enterprise-work-profile-bypass.md ../../linux-hardening/privilege-escalation/android-rooting-frameworks-manager-auth-bypass-syscall-hook.md {{#endref}} +{{#ref}} +abusing-android-media-pipelines-image-parsers.md +{{#endref}} {{#ref}} ../../binary-exploitation/linux-kernel-exploitation/arm64-static-linear-map-kaslr-bypass.md diff --git a/src/mobile-pentesting/android-app-pentesting/abusing-android-media-pipelines-image-parsers.md b/src/mobile-pentesting/android-app-pentesting/abusing-android-media-pipelines-image-parsers.md new file mode 100644 index 00000000000..da957d08ff8 --- /dev/null +++ b/src/mobile-pentesting/android-app-pentesting/abusing-android-media-pipelines-image-parsers.md @@ -0,0 +1,78 @@ +# Abusing Android Media Pipelines & Image Parsers + +{{#include ../../banners/hacktricks-training.md}} + +## 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. + +```bash +$ 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=5125` and `planes=5123` even though Stage-3 images only expose planes 0–2 (RGB). +- 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. +- 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. + +### Turning increments into arbitrary writes +- The exploit first corrupts Stage-3 `QuramDngImage.bottom/right` using 480 malformed `DeltaPerColumn` operations so future opcodes treat enormous coordinates as in-bounds. +- `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. +- 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): +- `QuramDngImage` descriptors for Stage 1/2/3 +- `QuramDngOpcodeTrimBounds` and vendor `Unknown` opcodes (ID ≥14, including ID 23) + +The exploit sequences allocations to deterministically place chunks: +1. Stage-1 `Unknown(23)` opcodes (20,000 entries) spray 0x30 chunks that later get freed. +2. Stage-2 frees those opcodes and places a new `QuramDngImage` inside the freed region. +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. +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. +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. + +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: +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. +2. Patch the rest of the `TrimBounds` fields (top/left/width/planes) so the object behaves like a valid `MapTable` when executed later. +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. +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. + +## Triggering the JOP ➜ `system()` Transition + +Once the gadget pointers and shell command are staged inside the opcode spray: +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. +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. +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()` inside `com.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](https://projectzero.google/2025/12/android-itw-dng.html) + +{{#include ../../banners/hacktricks-training.md}}