Another day, another zero-day. This time it’s CVE-2025-21043, a critical vulnerability in Android’s DNG image parser that’s been actively exploited in the wild. What makes this one particularly interesting is how it leverages an obscure feature of the DNG formatβopcode listsβto achieve remote code execution.
Following our previous analysis of CVE-2025-43300 and the ELEGANTBOUNCER detection framework, let’s dive into how this vulnerability works and why it matters.
The Discovery π
On September 2025, Samsung just pushed a critical security update. The advisory was sparse on details, but one line caught everyone’s attention:
“Samsung was notified that an exploit for this issue has existed in the wild.”
The vulnerability was reported by Meta and WhatsApp security teams on August 13, suggesting this was part of the same campaign that targeted iOS users with CVE-2025-43300. According to security researchers, the attack impacted both iPhone and Android WhatsApp users, with civil society individuals among the targets.
Thanks to @__suto’s initial vulnerability analysis, we were able to pinpoint the exact issue: an out-of-bounds write in libimagecodec.quram.so
’s DNG parser, specifically in the QuramDngOpcodeList::parse
function.
Technical Background π
DNG Opcode Lists π
The DNG (Digital Negative) format, developed by Adobe, includes support for opcodes that allow image processing operations to be embedded directly in the file. These opcodes are stored in three possible TIFF tags:
- Tag 0xC740 (
OpcodeList1
): Applied to raw image data - Tag 0xC741 (
OpcodeList2
): Applied after demosaicing - Tag 0xC74E (
OpcodeList3
): Applied after color space conversion
Opcode List Structure π
Each opcode list follows a specific binary structure:
[4 bytes] Opcode Count (Big-Endian)
[Variable] Opcode Data Array
The opcode count is stored as a 32-bit unsigned integer in big-endian byte order, regardless of the TIFF file’s byte order (which can be little-endian or big-endian).
Opcode Types π
DNG supports several opcode types, each with a unique identifier:
- WarpRectilinear (1): Geometric correction for rectilinear lenses
- WarpFisheye (2): Geometric correction for fisheye lenses
- FixVignetteRadial (3): Radial vignetting correction
- FixBadPixelsConstant (4): Replace bad pixels with constant value
- FixBadPixelsList (5): Replace bad pixels from a list
- TrimBounds (6): Crop the image
- MapTable (7): Apply lookup table transformation
- MapPolynomial (8): Apply polynomial transformation
- GainMap (9): Apply gain map for lens shading correction
- DeltaPerRow (10): Row-wise delta encoding
- DeltaPerColumn (11): Column-wise delta encoding
- ScalePerRow (12): Row-wise scaling
- ScalePerColumn (13): Column-wise scaling
Each opcode has its own parameter structure following the opcode ID in the data stream.
Opcode Processing Flow π
graph TD
DNG[DNG File] --> Parser[TIFF Parser]
Parser --> Tag[Opcode List Tag<br/>0xC740/0xC741/0xC74E]
Tag --> Count[Read Opcode Count<br/>4 bytes, Big-Endian]
Count --> Validate[Count Validation Check]
Validate -->|No Check<br/>VULNERABLE| Alloc[Allocate Memory<br/>count * sizeof opcode]
Validate -->|Count > 1M<br/>FIXED| Error[Throw Error]
Alloc --> Overflow[Integer Overflow<br/>0xFFFFFFFF * size]
Overflow --> Small[Small Allocation]
Small --> Write[Process Opcodes]
Write --> OOB[Out-of-Bounds Write]
OOB --> RCE[Remote Code Execution]
style Count fill:#fff9c4
style Validate fill:#ffccbc
style Overflow fill:#ffcdd2
style OOB fill:#ff8a65
style RCE fill:#d32f2f,color:#fff
The Vulnerability π
Root Cause π
The Quram DNG parser in Android’s libimagecodec.quram.so
fails to properly validate the opcode count before allocating memory and processing opcodes. Thanks to
qriousec for the decompilation, the vulnerable code path kind of looks like this:
// Simplified vulnerable code pattern
uint32_t opcode_count = QuramDngStream_get_QMUINT32(stream);
// Missing bounds check in vulnerable version!
// Fixed version adds:
if (opcode_count > 1000000) {
__android_log_print(6, "QURAMDNG_N",
"[%s:%d] Invalid opcode count: %u (max: %u)",
"parse", 74, opcode_count, 1000000);
Throw_dng_error(0xFFFFD8F6, "Invalid opcode count", 0, 0);
}
// Vulnerable allocation without proper bounds checking
opcode_array = allocate_memory(opcode_count * sizeof(opcode_structure));
Attack Vector π
An attacker can craft a malicious DNG file with an extremely large opcode count (e.g., 0xFFFFFFFF) that causes:
- Integer Overflow:
opcode_count * sizeof(opcode_structure)
can overflow - Heap Corruption: Subsequent opcode processing writes beyond allocated buffer
- Memory Corruption: Leading to potential code execution
Vulnerability Timeline π
graph LR
A[Attacker crafts<br/>malicious DNG] --> B[Sets opcode count<br/>to 0xFFFFFFFF]
B --> C[Sends via<br/>WhatsApp]
C --> D[Auto-download<br/>enabled]
D --> E[Thumbnail<br/>generation]
E --> F[Parser reads<br/>opcode count]
F --> G[No validation<br/>β]
G --> H[Integer overflow<br/>in allocation]
H --> I[Heap corruption]
I --> J[Code execution]
style A fill:#ffe0b2
style B fill:#ffccbc
style C fill:#ffab91
style G fill:#ff8a65
style H fill:#ff7043
style I fill:#ff5722
style J fill:#d32f2f,color:#fff
Detection Algorithm π
Our Implementation π
The ELEGANTBOUNCER detection algorithm for CVE-2025-21043 works as follows:
fn check_opcode_list(entry: &IFDEntry) -> bool {
const MAX_OPCODE_COUNT: u32 = 1000000;
// Opcode lists must be at least 4 bytes (count field)
if entry.count < 4 {
return true; // Too small, not vulnerable
}
// Read opcode count based on data location
let opcode_count = if entry.count <= 4 {
// Data is inline in value_offset field
entry.value_offset.to_be() // Convert to big-endian
} else {
// Data is at offset, need to seek and read
seek(entry.value_offset);
read_u32_be() // Read as big-endian
};
// Check against threshold
if opcode_count > MAX_OPCODE_COUNT {
log_detection("CVE-2025-21043: Excessive opcode count");
return false; // Vulnerable!
}
true // Safe
}
ELEGANTBOUNCER Detection Flow π
flowchart TD
Start[Parse DNG File] --> ScanIFD[Scan IFD Entries]
ScanIFD --> CheckTag{Is Opcode<br/>List Tag?}
CheckTag -->|No| NextTag[Next Tag]
NextTag --> ScanIFD
CheckTag -->|Yes<br/>0xC740/0xC741/0xC74E| CheckSize{Data Size<br/>< 4 bytes?}
CheckSize -->|Yes| Safe1[Too small<br/>Not vulnerable]
CheckSize -->|No| Location{Data Location?}
Location -->|Inline| ReadInline[Read from<br/>value_offset field]
Location -->|Offset| ReadOffset[Seek to offset<br/>Read 4 bytes]
ReadInline --> Convert[Convert to<br/>Big-Endian]
ReadOffset --> Convert
Convert --> Threshold{Count ><br/>1,000,000?}
Threshold -->|No| Safe2[β Safe]
Threshold -->|Yes| Detect[π¨ CVE-2025-21043<br/>Detected!]
Detect --> Log[Log Detection<br/>Alert User]
style CheckTag fill:#e3f2fd
style Convert fill:#fff9c4
style Threshold fill:#ffccbc
style Detect fill:#ff5252,color:#fff
style Safe1 fill:#c8e6c9
style Safe2 fill:#c8e6c9
Detection Logic π
- Tag Identification: Scan for tags 0xC740, 0xC741, and 0xC74E in all IFDs and SubIFDs
- Data Location: Handle both inline data (β€4 bytes) and offset-based data
- Endianness Handling: Always interpret opcode count as big-endian
- Threshold Check: Flag files with opcode_count > 1,000,000 as malicious
Key Detection Points π
- Primary IFDs: Check main image IFDs
- SubIFDs: Check thumbnail and preview SubIFDs
- Multiple Tags: A file can have all three opcode lists
- Byte Order: Opcode count is always big-endian, even in little-endian TIFF files
Real-World Implications π
Attack Scenarios π
- Messaging Apps: Malicious DNG shared via messaging platforms (Like WhatsApp)
- Photo Libraries: Automatic processing when importing photos
- Cloud Services: Server-side processing of uploaded images
- Gallery Apps: Thumbnail generation triggering the vulnerability
Affected Systems π
- Android devices with vulnerable Quram DNG parser
- Specifically impacts
libimagecodec.quram.so
- Affects DNG processing in camera and gallery applications
Mitigation π
Vendor Fix π
The official fix adds a bounds check:
if (opcode_count > 1000000) {
// Reject the file
throw_error("Invalid opcode count");
}
Detection Strategy π
Files should be scanned for:
- Presence of opcode list tags
- Opcode count values exceeding reasonable thresholds
- Suspicious patterns in opcode data
Sample Detection Output π
When ELEGANTBOUNCER detects this vulnerability:
[!] CVE-2025-21043: Excessive opcode count detected: 4294967295 (max: 1000000)
[!!!] CVE-2025-21043: Excessive opcode count in tag 0xC740
[!!!] CVE-2025-21043 detected: Excessive opcode count in DNG file
Technical Details π
Memory Layout Example π
Offset | Data | Description |
---|---|---|
0x0000 |
00 00 10 00 |
Opcode count (4096 in big-endian) |
0x0004 |
00 00 00 09 |
Opcode type (GainMap) |
0x0008 |
[parameters...] |
GainMap parameters |
... |
... |
More opcodes |
Exploitation Primitive π
The vulnerability provides:
- Controlled allocation size: Via opcode count
- Controlled write size: Via subsequent opcode data
- Heap shaping opportunity: Via TIFF tag ordering
Research Notes π
Interesting Observations π
- Endianness Quirk: Opcode count uses fixed big-endian regardless of TIFF byte order
- Threshold Selection: 1,000,000 opcodes is far beyond any legitimate use case
- Tag Multiplicity: All three opcode lists can exist in a single file
- Processing Order: OpcodeList1 β OpcodeList2 β OpcodeList3
Legitimate Opcode Counts π
In practice, legitimate DNG files rarely contain more than:
- 10-20 opcodes for lens corrections
- 50-100 opcodes for complex geometric corrections
- 500 opcodes in extreme professional editing scenarios
The 1,000,000 threshold provides a massive safety margin while preventing exploitation.
Attack vs Defense π
graph TD
subgraph Attack["π΄ Attack Chain"]
A1[Craft DNG] --> A2[Set huge opcode count]
A2 --> A3[Send to target]
A3 --> A4[Trigger parsing]
A4 --> A5[Achieve RCE]
end
subgraph Defense["π’ Defense Layers"]
D1[ELEGANTBOUNCER<br/>Pre-scan] --> D2[Detect excessive<br/>opcode count]
D2 --> D3[Block malicious file]
D3 --> D4[Alert and quarantine]
D4 --> D5[Prevent exploitation]
end
Attack -.->|Blocked by| Defense
style A1 fill:#ffcdd2
style A5 fill:#d32f2f,color:#fff
style D1 fill:#c8e6c9
style D5 fill:#4caf50,color:#fff
Conclusion π
CVE-2025-21043 represents a critical vulnerability in DNG processing that could lead to remote code execution. The detection algorithm implemented in ELEGANTBOUNCER efficiently identifies potentially malicious files by checking opcode counts against reasonable thresholds, providing protection against this actively exploited vulnerability.
The simplicity of the vulnerability (missing bounds check) combined with the complexity of the DNG format makes it an attractive target for attackers, highlighting the importance of proper input validation in image parsing libraries.
References π
- ELEGANTBOUNCER GitHub Repository
- Adobe DNG Specification 1.7
- Android Security Bulletin (CVE-2025-21043)
- Quram DNG Parser Implementation Analysis
- TIFF 6.0 Specification (for IFD structure)