home intel mediatek-da2-usb-heap-overflow-arbitrary-code-execution
CVE Analysis 2025-04-07 · 8 min read

CVE-2025-20658: MediaTek DA2 USB Handler Heap Overflow → ACE

A logic error in MediaTek's Download Agent USB command handler allows heap overflow via a malformed USB packet, enabling arbitrary code execution with physical access.

#permission-bypass#privilege-escalation#logic-error#physical-access#local-vulnerability
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2025-20658 · Vulnerability
ATTACKERCross-platformVULNERABILITYCVE-2025-20658MEDIUMSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

CVE-2025-20658 is a heap overflow in MediaTek's Download Agent (DA) firmware, specifically in the DA2 USB command handler. The bug lives in the pre-boot environment used for flashing and factory provisioning — a high-value target because it runs before the main OS, has no ASLR, and on many devices exposes a full-featured USB interface with no authentication by default. An attacker with physical USB access can send a crafted sequence of DA commands to overflow a heap buffer, corrupt adjacent allocations, and achieve arbitrary code execution inside the DA runtime.

CVSS scores this at 6.0 (Medium) because physical access is required. In practice, for a forensics lab, a border crossing, or a device repair shop, "physical access" is the entire threat model. Patch ID ALPS09474894 / MSV-2597.

Affected Component

The Download Agent is a small ARM Cortex-A BLOB pushed over USB to the device's SRAM/DRAM by the host-side flashing tool (SP Flash Tool, MTK Client, etc.) during boot ROM stage. Once loaded by BootROM, DA takes ownership of the USB interface and processes a structured command protocol. DA2 is the second-generation DA used across MT67xx, MT68xx, and MT69xx SoCs. The vulnerable function resides in the USB bulk-transfer receive path, specifically the subroutine that reassembles multi-packet payloads into a contiguous heap buffer.

Root cause: The DA2 USB receive loop allocates a heap buffer sized from an attacker-supplied advertised_length field, but reads data in fixed-size chunks without clamping the final chunk to the remaining bytes, allowing a write of up to packet_size - 1 bytes past the end of the allocation.

Root Cause Analysis

The vulnerable function, reconstructed from DA2 binary analysis, is da2_usb_recv_payload. The host sends a command packet whose first four bytes encode the total expected payload length. DA allocates exactly that many bytes, then enters a receive loop consuming USB_BULK_PKT_SIZE (0x200) chunks per iteration:


// da2_usb_recv_payload — reconstructed from DA2 binary (MT6789, 2024Q3)
// Called from: da2_dispatch_command() for any variable-length payload command

int da2_usb_recv_payload(usb_ctx_t *ctx, uint8_t **out_buf, uint32_t *out_len) {
    uint32_t advertised_len = 0;
    uint32_t bytes_read     = 0;
    uint32_t chunk_size     = 0;
    uint8_t *buf            = NULL;

    // Read 4-byte little-endian length prefix from host
    usb_bulk_read(ctx, (uint8_t *)&advertised_len, 4);

    // Allocate exactly advertised_len bytes — attacker-controlled size
    buf = (uint8_t *)heap_alloc(advertised_len);
    if (!buf) return DA_ERR_NOMEM;

    *out_buf = buf;
    *out_len = advertised_len;

    while (bytes_read < advertised_len) {
        chunk_size = USB_BULK_PKT_SIZE;  // BUG: always 0x200, never clamped to remaining bytes

        // BUG: if (advertised_len % 0x200 != 0), final iteration reads 0x200 bytes
        //      but only (advertised_len - bytes_read) bytes of space remain in buf.
        //      Overflow = 0x200 - (advertised_len % 0x200) bytes past allocation end.
        usb_bulk_read(ctx, buf + bytes_read, chunk_size);

        bytes_read += chunk_size;  // BUG: advances by full chunk, not actual bytes written
    }

    return DA_OK;
}

The overflow window is bounded: maximum overshoot is USB_BULK_PKT_SIZE - 1 = 0x1FF bytes. But 511 bytes of controlled write past a heap allocation is more than sufficient to corrupt a subsequent chunk header on the DA heap, whose allocator uses inline metadata.

Memory Layout

DA2 uses a simple first-fit heap allocator with the following chunk header layout:


// DA2 heap chunk header — inline, precedes user data
struct da2_chunk_hdr {
    /* +0x00 */ uint32_t  magic;       // 0xDA2C0DE5 when allocated, 0xDA2FFREE when free
    /* +0x04 */ uint32_t  size;        // total chunk size including this header
    /* +0x08 */ uint32_t  flags;       // bit0: in-use, bit1: last-chunk
    /* +0x0C */ uint32_t  checksum;    // simple XOR of above three fields
    /* +0x10 */ uint8_t   data[];      // user data starts here
};

The allocator's heap_free() trusts chunk->size to locate the next chunk for coalescing, and trusts chunk->magic to validate the block. Corrupting either field gives controlled writes during the next free/alloc operation.

A typical heap layout when processing a CMD_WRITE_PARTITION command of size 0x3C0 bytes (which leaves a 0x40-byte remainder on the final 0x200-chunk read):


DA2 HEAP STATE — before overflow (advertised_len = 0x3C0):

  0x40090000  [ da2_chunk_hdr: magic=0xDA2C0DE5 size=0x3D0 flags=0x1 ]
  0x40090010  [ payload buf: 0x3C0 bytes of user data              ]  <-- buf
  0x400903D0  [ GUARD: 0x40 bytes padding to chunk boundary        ]

  0x40090400  [ da2_chunk_hdr: magic=0xDA2C0DE5 size=0x60  flags=0x1 ]  <-- next alloc (cmd_ctx)
  0x40090410  [ cmd_ctx: fn_ptr=0x400A1234, arg=0x400B5678, ...    ]

DA2 HEAP STATE — after overflow (3 iterations × 0x200 = 0x600 read, 0x240 past end):

  0x40090000  [ da2_chunk_hdr: intact                              ]
  0x40090010  [ payload buf: bytes 0x000–0x3BF: legit data         ]
  0x400903D0  [ bytes 0x3C0–0x3FF: attacker data (0x40 byte ovfl) ] — into guard
  0x40090400  [ CORRUPTED da2_chunk_hdr:                           ]
                magic    = 0x41414141  (attacker-controlled)
                size     = 0x41414242  (attacker-controlled)
                flags    = 0x41414343  (attacker-controlled)
                checksum = 0x41414444  (attacker-controlled)
  0x40090410  [ cmd_ctx: fn_ptr CORRUPTED = 0x40100000 (shellcode) ]

Exploitation Mechanics

The heap spray target of choice is a cmd_ctx structure allocated immediately after the payload buffer when DA2 processes a two-phase command (write-then-execute pattern). The fn_ptr field at cmd_ctx+0x00 is called directly by the command dispatch loop after the write phase completes.


EXPLOIT CHAIN — CVE-2025-20658:

1. Boot device into BROM mode (hold BOOT key or short BootROM pins).
   Device enumerates as MediaTek USB VCOM (0x0E8D:0x0003).

2. Connect with modified MTK client. Send DA2 binary to BROM via
   CMD_SEND_DA. BROM loads DA2 to SRAM and jumps to entry point.

3. Send DA2 handshake (SYNC bytes 0xA0 0x0A 0x50 0x05). DA2 ACKs.

4. Spray heap: send 8× CMD_DOWNLOAD_BROM with payload size=0x3C0.
   Each creates: [chunk_hdr][0x3C0 payload][cmd_ctx] pattern.
   Heap layout becomes deterministic due to fixed-size DA2 allocator.

5. Send CMD_DOWNLOAD_BROM with advertised_len=0x3C0 but pipe
   0x600 bytes of data (3 full USB bulk packets):
     Bytes 0x000–0x3BF: padding (0x41 fill)
     Bytes 0x3C0–0x3FF: heap guard region (0x41 fill)
     Bytes 0x400–0x40F: fake da2_chunk_hdr
       magic    = 0xDA2C0DE5  (valid, bypasses check)
       size     = 0x00000060  (correct, preserves coalesce)
       flags    = 0x00000001  (in-use)
       checksum = XOR of above = 0xDA2C0DA4
     Bytes 0x410–0x417: overwrite cmd_ctx.fn_ptr = &shellcode_buf
     Bytes 0x418–0x5FF: padding

6. DA2 completes receive loop (3 iterations). cmd_ctx.fn_ptr now
   points to attacker-controlled shellcode_buf (pre-loaded in step 4
   as a legitimate payload in a prior allocation).

7. DA2 dispatch loop calls cmd_ctx->fn_ptr(cmd_ctx->arg).
   Execution transfers to shellcode. DA2 runs in EL1/SVC mode.
   Full SoC memory access. No ASLR. No stack canaries in DA2.

8. Shellcode: disable AES key fuse read-lock, dump RPMB keys,
   or install persistent bootloader backdoor.

Patch Analysis

The fix in ALPS09474894 is minimal and correct: clamp chunk_size to the remaining bytes before each usb_bulk_read call, and change the loop termination condition from != to < to prevent integer equality bypass via overshooting.


// BEFORE (vulnerable) — da2_usb_recv_payload, pre-ALPS09474894:
while (bytes_read < advertised_len) {
    chunk_size = USB_BULK_PKT_SIZE;           // always 0x200
    usb_bulk_read(ctx, buf + bytes_read, chunk_size);
    bytes_read += chunk_size;                 // overshoots on final iteration
}

// AFTER (patched) — da2_usb_recv_payload, ALPS09474894:
while (bytes_read < advertised_len) {
    uint32_t remaining = advertised_len - bytes_read;
    chunk_size = (remaining < USB_BULK_PKT_SIZE) ? remaining : USB_BULK_PKT_SIZE;
    usb_bulk_read(ctx, buf + bytes_read, chunk_size);
    bytes_read += chunk_size;                 // now bounded by remaining bytes
}

A secondary hardening change also added a checksum validation in heap_free() before trusting chunk->size, making heap metadata corruption harder to weaponize even if a similar overflow were discovered elsewhere. This is defense-in-depth and not the primary fix.

Detection and Indicators

Detection is difficult post-exploitation because DA runs pre-OS and writes no logs to persistent storage. The following indicators apply during active exploitation:

  • USB traffic anomaly: Host sends more data bytes than declared in the 4-byte length prefix. Capture with Wireshark + USBPcap; filter on usb.data_len > usb.transfer.data_flag.
  • BROM enumeration on non-factory device: USB VID/PID 0x0E8D:0x0003 appearing on a device not in factory provisioning is suspicious. Monitor USB enumeration logs on shared workstations.
  • DA binary not matching known-good hash: A modified DA that exploits the device can be fingerprinted. Maintain a hash database of released DA BLOBs per SoC.
  • Post-exploitation: RPMB key exfiltration would manifest as unexpected CMD_SEND_PARTITION_DATA traffic toward the host after the expected flash sequence completes.

Remediation

Apply the MediaTek security bulletin patch for your SoC family (March 2025 bulletin, patch ID ALPS09474894). Verify the DA binary version matches or exceeds the patched build before accepting OTA updates that include DA refreshes.

If patching is not immediately available:

  • Physical access controls are the primary mitigation. DA mode requires device reboot into BROM; disk encryption and secure boot do not prevent this.
  • DA authentication (if supported by your SoC): some MT68xx devices support an RSA-signed DA handshake. Enable it in the manufacturing fuse configuration to reject unsigned DA images.
  • Fuse JTAG/download mode disable in production devices: blow the SBC_EN fuse to enforce authenticated boot and reject unsigned DA payloads entirely. This is irreversible.
CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →