Summary

Product Microsoft DirectMusic
Vendor Microsoft
Severity High
Affected Versions Microsoft DirectMusic Core Services DLL (dmusic.dll) version 10.0.22000.1
Tested Versions Microsoft DirectMusic Core Services DLL (dmusic.dll) version 10.0.22000.1
CVE Identifier CVE-2022-44667

CVSS3.1 Scoring System

Base Score: 7.8 (High)
Vector String: CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

Metric Value
Attack Vector (AV) Local
Attack Complexity (AC) Low
Privileges Required (PR) None
User Interaction (UI) Required
Scope (S) Unchanged
Confidentiality (C) High
Integrity (I) High
Availability (A) High

Product Overview

Microsoft DirectMusic Core Services DLL is a dynamic link library (DLL) that is part of the DirectMusic component of the DirectX multimedia API for Windows operating systems. DirectMusic is a high-level music composition and playback system designed to simplify the process of creating and playing back music in Windows-based multimedia applications.

The DirectMusic Core Services DLL provides essential services for managing and playing back MIDI and digital audio data in DirectMusic applications. It contains functions for managing MIDI and audio devices, synthesizers, and sound cards, as well as for loading and playing back MIDI and audio data.

Vulnerability Description

A vulnerability has been found in the dmusic.dll library. This vulnerability can be used by an attacker to cause an integer overflow through a specifically crafted application. As a result, when a DLS file is loaded and parsed, an out-of-bounds write can occur in the IDirectMusicPort COM component.

An attacker can exploit this vulnerability to remotely execute code on the victim’s machine. Below is the crash context:

(6f58.6d24): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
msvcrt!memcpy+0x1e3:
00007ffb`ebb77f23 0f2b49c0        movntps xmmword ptr [rcx-40h],xmm1 ds:00000267`d2026000=????????????????????????????????
0:000> k
 # Child-SP          RetAddr           Call Site
00 000000cf`96cfeec8 00007ffb`a425280c msvcrt!memcpy+0x1e3
01 000000cf`96cfeed0 00007ffb`a42578f6 dmusic!CArticulation::Write+0xa8
02 000000cf`96cfef30 00007ffb`a4258b7e dmusic!CInstrObj::Write+0x34a
03 000000cf`96cfefc0 00007ffb`a425a5a7 dmusic!CDirectMusicPortDownload::DownloadP+0x61e
*** WARNING: Unable to verify checksum for dsmusic.exe
04 000000cf`96cff0a0 00007ff7`2957143d dmusic!CDirectMusicSynthPort::DownloadInstrument+0x47
05 (Inline Function) --------`-------- dsmusic!DownloadAndUnload+0x43 [E:\harness\dsmusic\dsmusic\Source.cpp @ 44] 
06 000000cf`96cff0e0 00007ff7`29571750 dsmusic!wmain+0x3cd [E:\harness\dsmusic\dsmusic\Source.cpp @ 96] 
07 (Inline Function) --------`-------- dsmusic!invoke_main+0x22 [d:\agent\_work\4\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 90] 
08 000000cf`96cff9b0 00007ffb`ec3854e0 dsmusic!__scrt_common_main_seh+0x10c [d:\agent\_work\4\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288] 
09 000000cf`96cff9f0 00007ffb`ecea485b KERNEL32!BaseThreadInitThunk+0x10
0a 000000cf`96cffa20 00000000`00000000 ntdll!RtlUserThreadStart+0x2b
0:000> ?rcx
Evaluate expression: 2644928258112 = 00000267`d2026040
0:000> ?xmm1
Evaluate expression: -4557430888798830400 = c0c0c0c0`c0c0c0c0
0:000> !heap -p -a rcx
    address 00000267d2026040 found in
    _DPH_HEAP_ROOT @ 26571f41000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                             26571f466e8:      267a5290140         2cd95ec0 -      267a5290000         2cd97000
    00007ffbecfa400c ntdll!RtlDebugAllocateHeap+0x0000000000000048
    00007ffbecf5d750 ntdll!RtlpAllocateHeap+0x0000000000092780
    00007ffbecec910c ntdll!RtlpAllocateHeapInternal+0x00000000000006ac
    00007ffbebb1c750 msvcrt!malloc+0x0000000000000070
    00007ffba42584a4 dmusic!CDirectMusicPortDownload::AllocateBuffer+0x0000000000000044
    00007ffba4258b14 dmusic!CDirectMusicPortDownload::DownloadP+0x00000000000005b4
    00007ffba425a5a7 dmusic!CDirectMusicSynthPort::DownloadInstrument+0x0000000000000047
    00007ff72957143d dsmusic!wmain+0x00000000000003cd [E:\harness\dsmusic\dsmusic\Source.cpp @ 96]
    00007ff729571750 dsmusic!__scrt_common_main_seh+0x000000000000010c [d:\agent\_work\4\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
    00007ffbec3854e0 KERNEL32!BaseThreadInitThunk+0x0000000000000010
    00007ffbecea485b ntdll!RtlUserThreadStart+0x000000000000002b

 
0:000> db rcx
00000267`d2026040  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00000267`d2026050  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00000267`d2026060  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00000267`d2026070  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00000267`d2026080  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00000267`d2026090  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00000267`d20260a0  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00000267`d20260b0  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

The vulnerability lies in the implementation of the following COM class:

  • Interface: IDirectMusicCollection (480FF4B0-28B2-11D1-BEF7-00C04FBF8FEF)
  • Interface: IDirectMusicPort
  • Server: C:\WINDOWS\System32\dmusic.dll

This COM class is a part of DirectMusic, which is a software module that allows for the loading and parsing of downloadable sound (DLS) files. DLS files contain different chunks and lists, such as Collection, Wave Pool, Wave File, List of Instrument, List of Region, List of Articulators, and others. The vulnerability can be observed in the dmusic.dll file.

CArticulation::Load(CRiffParser *)
CCollection::Load(IStream *)
CCopyright::Load(CRiffParser *)
CExtensionChunk::Load(CRiffParser *)
CConditionChunk::Load(CRiffParser *)
CInstrObj::Load(ulong,CRiffParser *,CCollection *)
CRegion::Load(CRiffParser *)
CWaveObj::Load(ulong,CRiffParser *,CCollection *)

Each chunk and list in the DLS file of dmusic.dll has its own unique structure. In this context, we will focus on the parsing of the List of Articulators.

To achieve this, we need to examine the function CArticulation::Load. This function is responsible for loading the articulation data from the DLS file. By analyzing this function, we can gain a better understanding of how the List of Articulators is parsed and structured within the DLS file.

__int64 __fastcall CArticulation::Load(CArticulation *this, struct CRiffParser *a2)
{
  ....
  v2 = a2->m_pChunk;
  pHr = 0;
  v20.lRead = 0;
  v20.pParent = v2;
  a2->m_pParent = v2;
  a2->m_pChunk = &v20;
  a2->m_fFirstPass = 1;
  while ( CRiffParser::NextChunk(a2, &pHr) )
  {
    if ( v20.ckid == ' ldc' ) // parse Condition Chunk
    {
      pHr = CConditionChunk::Load(&this->m_Condition, a2);
    }
    else if ( ((v20.ckid - '1tra') & 0xFEFFFFFF) != 0 ) // parse Extension Chunk
    {
      ....
      ...
    }
    else                                        // parse Articulators
    {
      pOldConnections = this->m_ArticData.m_pConnections;
      dwOldCount = this->m_ArticData.m_ConnectionList.cConnections;
      if ( a2->m_pChunk )
      {
        hr = CRiffParser::Read(a2, &this->m_ArticData, 8u); // [1] read CONNECTIONLIST
        v10 = a2->m_pChunk;
      }
      else
      {
        hr = 0x80004005;
        v10 = 0i64;
      }
      if ( hr >= 0 )
      {
        cConnections = this->m_ArticData.m_ConnectionList.cConnections;
        dwSize = 12 * cConnections; //[2] integer overflow
        if ( 12 * cConnections <= (unsigned __int64)(v10->cksize - 8i64) ) //[3] compare with chunk size
        {
          v13 = 12i64 * (cConnections + (unsigned int)dwOldCount);
          if ( !is_mul_ok(cConnections + (unsigned int)dwOldCount, 0xCui64) ) //[4] check overflow on __int64
            v13 = -1i64;
          v14 = 1i64;
          if ( v13 )
            v14 = v13;
          m_pConnections = (CONNECTION *)malloc(v14); // allocate memory area m_pConnections with size v14 = 12*cConnections
          this->m_ArticData.m_pConnections = m_pConnections;
          if ( m_pConnections )
          {
            if ( a2->m_pChunk )
              hr = CRiffParser::Read(a2, m_pConnections, dwSize); //[5] read data of size dwSize saves to m_pConnections
            else
              hr = 0x80004005;
            if ( hr < 0 )
            {
              free(this->m_ArticData.m_pConnections);
              this->m_ArticData.m_pConnections = pOldConnections;
              this->m_ArticData.m_ConnectionList.cConnections = dwOldCount;
            }
            else if ( pOldConnections && (_DWORD)dwOldCount )
            {
              memcpy_0(
                &this->m_ArticData.m_pConnections[this->m_ArticData.m_ConnectionList.cConnections],
                pOldConnections,
                12 * dwOldCount);
              this->m_ArticData.m_ConnectionList.cConnections += dwOldCount;
            }
          }
          else
          {
            hr = 0x8007000E;
          }
        }
        else
        {
          hr = 0x80004005;
        }
      }
      pHr = hr;
    }
  }
  ....
}

The CArticulation::Load function performs parsing of chunks using tags, with one of these chunks being the Articulators List. When executed, the function will extract the relevant data from the Articulators List and store it in m_ArticData. This is achieved by reading 8 bytes from the DLS file that represent CONNECTIONLIST is commented at [1].

The CONNECTIONLIST structure is defined as follows:

struct _CONNECTIONLIST
{
  ULONG cbSize;
  ULONG cConnections;
};

Let’s focus on how the code is handling the cConnections field in the CONNECTIONLIST struct. The dwSize variable is assigned a value of 12*cConnections, which is of type DWORD and is commented at [2]. However, if cConnections is large enough, the 12*cConnections calculation may exceed the size of DWORD.

The code then checks whether 12*cConnections is less than or equal to cksize-8, which is the size of the Articulation List and is commented at [3]. If this condition is satisfied, the function allocates a memory area with a size of 12*cConnections. Although the code has an overflow check when doing the 12*cConnections multiplication, it is only checking for overflow in __int64, so the multiplication will not overflow, as noted at [4].

Once the memory area is allocated, the code reads data into the newly allocated m_pConnections memory area with a size of dwSize. Although there is an integer overflow here, the m_pConnections memory area is allocated with a much larger size than dwSize, so there should be no crash.

Additionally, we have a CInstrObj::Size function that calculates the total size of InstrObj, including the ExtensionChunkList, ArticulationList, RegionList, and CopyrightChunk. We will focus on the section that calculates the size of the ArticulationList.

__int64 __fastcall CInstrObj::Size(CInstrObj *this, unsigned int *pdwSize)
{
  ....
  ....
  pArticulation = (CArticulation *)this->m_ArticulationList.m_pHead;
  while ( pArticulation )
  {
    do
    {
      if ( (unsigned int)CArticulation::Count(pArticulation) )
        break;
      pArticulation = (CArticulation *)pArticulation->m_pNext.m_pNext;
    }
    while ( pArticulation );
    if ( pArticulation )
    {
      m_dwSize = CArticulation::Size(pArticulation) + this->m_dwSize;// integer overflow
      v10 = m_dwSize;
      this->m_dwSize = m_dwSize;
      v8 = CArticulation::Count(pArticulation) + this->m_dwNumOffsetTableEntries;
      this->m_dwNumOffsetTableEntries = v8;
      if ( !this->m_fNewFormat )
        break;
      pArticulation = (CArticulation *)pArticulation->m_pNext.m_pNext;
    }
    else
    {
      m_dwSize = this->m_dwSize;
      v10 = m_dwSize;
      v8 = this->m_dwNumOffsetTableEntries;
    }
  }
  ....
  ....
  else
  {
    v23 = v8;
  }
  v24 = m_dwSize + ((4 * v23 + 7) & 0xFFFFFFF8);
  this->m_dwSize = v24;
  *pdwSize = v24;
  return v3;
}

This code performs a loop to calculate the total size of all the Articulators. During each loop, the function CArticulation::Size is called to obtain the size of the current Articulator. This size is then added to this->m_dwSize, and the result is returned through the unsigned int * parameter. pdwSize.

Be aware of the line m_dwSize = CArticulation::Size(pArticulation) + this->m_dwSize;. Both m_dwSize and this->m_dwSize are of type DWORD, but CArticulation::Size returns a __int64 value. An integer overflow bug may occur when the resulting value is larger than the maximum size of a DWORD.

The CInstrObj::Size function is utilized in CDirectMusicPortDownload::DownloadP to determine the size of the InstrObj. Memory is allocated to accommodate this InstrObj. Finally, we see the function CDirectMusicPortDownload::DownloadP.

__int64 __fastcall CDirectMusicPortDownload::DownloadP(CDirectMusicPortDownload *this, struct IDirectMusicInstrument *a2, struct IDirectMusicDownloadedInstrument **a3, struct _DMUS_NOTERANGE *a4, unsigned int a5)
{
  ....
  ....
  if ( hr >= 0 && v8 )
  {
    v72 = 0i64;
    CDirectMusicPortDownload::GetBufferInternal(this, v62, &v72);
    v47 = a2->InstObj;
    v48 = v47->m_dwSize;
    if ( (_DWORD)v48 )
    {
      hr = 0;
    }
    else
    {
      v49 = CInstrObj::Size(v47, &pdwSize); //[6]
      v48 = pdwSize;
      hr = v49;
    }
    dBuffer = 0i64;
    if ( hr >= 0 )
    {
      hr = ((__int64 (__fastcall *)(CDirectMusicPortDownload *, __int64, CDownloadBuffer **))this->vftable__->CDirectMusicPortDownload::AllocateBuffer)(
             this,
             v48,
             &dBuffer);
      if ( hr < 0 )
        goto LABEL_114;
      ppvHeader = 0i64;
      dwHeaderSize = 0;
      v63 = 0;
      hr = ((__int64 (__fastcall *)(CDownloadBuffer *, data_dowloadinfo **, unsigned int *))dBuffer->vftbl_0_000000018001B258->CDownloadBuffer::GetBuffer)(
             dBuffer,
             &ppvHeader,
             &dwHeaderSize);
      if ( hr >= 0 )
      {
        v50 = a2->InstObj;
        v51 = v50->m_dwSize;
        if ( v51 )
          goto LABEL_106;
        if ( (int)CInstrObj::Size(v50, &v63) < 0 )
          goto LABEL_110;
        v51 = v63;
LABEL_106:
        if ( v51 > dwHeaderSize )
        {
LABEL_110:
          hr = -2005397244;
        }
        else
        {
          hr = CInstrObj::Write(a2->InstObj, ppvHeader);
          if ( hr >= 0 )
          {
  ....
  ....
}

The function mentioned here first calls CInstrObj::Size(v47, &pdwSize); to determine the total size of InstrObj. This size is saved to pdwSize and can be found at the commented line [6].

Next, the code calls CDirectMusicPortDownload::AllocateBuffer, which allocates a buffer called dBuffer with a size of pdwSize. The CDownloadBuffer::GetBuffer function is then used to split dBuffer into ppvHeader and dwHeaderSize, so that it can be processed.

After that, the value of ppvHeader is passed to the CInstrObj::Write function. This function in turn calls the CArticulation::Write function to copy data from the Articulators and save it to ppvHeader.

__int64 __fastcall CArticulation::Write(CArticulation *this, void *pv, unsigned int *pdwCurOffset, unsigned int *pDMWOffsetTable, unsigned int *pdwCurIndex, unsigned int dwNextArtIndex)
{
  ....
  ....
  v6 = *pdwCurOffset;
  if ( this->m_fNewFormat )
  {
    v10 = v6 + 16;
    *((_DWORD *)pv + 2) = dwNextArtIndex;
    v11 = 16;
    *pdwCurOffset = v6 + 16;
    if ( this->m_ArticData.m_pConnections )
      v12 = 12 * this->m_ArticData.m_ConnectionList.cConnections + 8;
    else
      v12 = 0;
    if ( v12 )
    {
      v13 = *pdwCurIndex;
      pDMWOffsetTable[v13] = v10;
      *(_DWORD *)pv = v13;
      *pdwCurIndex = v13 + 1;
      v14 = v10;
      if ( this->m_ArticData.m_pConnections )
      {
        *((_QWORD *)pv + 2) = this->m_ArticData.m_ConnectionList;
        memcpy_0( // heap overflow
          (char *)pv + 24,
          this->m_ArticData.m_pConnections,
          12i64 * this->m_ArticData.m_ConnectionList.cConnections);
        if ( this->m_ArticData.m_pConnections )
          v15 = 12 * this->m_ArticData.m_ConnectionList.cConnections + 8;
        else
          v15 = 0;
        v14 = v10 + v15;
        *pdwCurOffset = v10 + v15;
      }
      v11 = v14 - v10 + 16;
    }
    else
    {
      *(_DWORD *)pv = 0;
    }
  }
  ....
  ....
}

Let’s take a closer look at the function CArticulation::Write. In this function, data is copied from m_pConnections to pv + 24. pv + 24 is a memory area allocated in the CDirectMusicPortDownload::DownloadP function and is referred to as ppvHeader. However, due to an integer overflow bug that occurred when calculating the total size of InstObj, the allocated memory for ppvHeader may not be sufficient to hold the data of InstrObj. This can lead to an Out-of-Bounds write error in CArticulation::Write.

Credits

Lê Hữu Quang Linh (@linhlhq) of of STAR Labs SG Pte. Ltd. (@starlabs_sg)

Timeline:

  • 2022-07-29 Vendor Disclosure
  • 2022-07-29 Initial Vendor Contact
  • 2022-12-13 Vendor Patch Release