CVE-2025-21043: When DNG Opcodes Become Attack Vectors

Β· 1422 words Β· 7 minute read

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:

  1. WarpRectilinear (1): Geometric correction for rectilinear lenses
  2. WarpFisheye (2): Geometric correction for fisheye lenses
  3. FixVignetteRadial (3): Radial vignetting correction
  4. FixBadPixelsConstant (4): Replace bad pixels with constant value
  5. FixBadPixelsList (5): Replace bad pixels from a list
  6. TrimBounds (6): Crop the image
  7. MapTable (7): Apply lookup table transformation
  8. MapPolynomial (8): Apply polynomial transformation
  9. GainMap (9): Apply gain map for lens shading correction
  10. DeltaPerRow (10): Row-wise delta encoding
  11. DeltaPerColumn (11): Column-wise delta encoding
  12. ScalePerRow (12): Row-wise scaling
  13. 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:

  1. Integer Overflow: opcode_count * sizeof(opcode_structure) can overflow
  2. Heap Corruption: Subsequent opcode processing writes beyond allocated buffer
  3. 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 πŸ”—

  1. Tag Identification: Scan for tags 0xC740, 0xC741, and 0xC74E in all IFDs and SubIFDs
  2. Data Location: Handle both inline data (≀4 bytes) and offset-based data
  3. Endianness Handling: Always interpret opcode count as big-endian
  4. 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 πŸ”—

  1. Messaging Apps: Malicious DNG shared via messaging platforms (Like WhatsApp)
  2. Photo Libraries: Automatic processing when importing photos
  3. Cloud Services: Server-side processing of uploaded images
  4. 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:

  1. Presence of opcode list tags
  2. Opcode count values exceeding reasonable thresholds
  3. 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 πŸ”—

  1. Endianness Quirk: Opcode count uses fixed big-endian regardless of TIFF byte order
  2. Threshold Selection: 1,000,000 opcodes is far beyond any legitimate use case
  3. Tag Multiplicity: All three opcode lists can exist in a single file
  4. 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)