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