Table of Contents
Foreword
It’s been a long while since I last posted because it’s been kind of hard to find time in these few months, but I’m finally back with some good news. A few months back we got an update for the “interesting thing” I mentioned in my last post, and Microsoft rewarded us with a small bounty for it. What we found was a mp3 file with metadata that could crash windows explorer when you navigated to the folder containing the file. We expanded on it and did some analysis on what was likely to be causing the crash, so here’s a short writeup on it, enjoy!
Discovering The Crash
We realized that there was a consistent crash when we were triaging all the crashes reported by WinAFL and amazingly enough both of us managed to fuzz the same type of crash on separate machines. Since the auto-triager that we coded tested our crashes against a barebones parsing application, we thought that if it crashed there it would also potentially crash windows explorer, which reads the same IPropertyStore
object to grab metadata. When we accessed a folder containing our crash.mp3
, windows explorer closed on its own.
We also made sure to test it against an actual installed windows environment instead of purely testing it in VMs because there have been instances where we caught crashes that only worked within VMs. We also realized some peculiar things about the bug:
- It does not always crash windows explorer upon navigating to the POC folder. We had some theories on this but we felt it was a memory-dependent thing that we unfortunately did not understand much as windows explorer does not always end up reading OOB or even reading the metadata at all. However, it does crash on most of the tests (>95% of our tests).
- If the file was in the “Recent Files” section of windows explorer, it pretty much crashes windows explorer on startup.
- The file can still be read, opened etc, it only causes a problem when its
IPropertyStore
object is parsed.
Nevertheless, we still thought it was an interesting crash so we delved deeper to try to find out exactly where it was crashing.
WMF Vulnerability (CVE-2021-33760)
This is a vulnerability that allows for an out-of-bounds read, which leads to information disclosure.
Revision record
Date | Revision Version | Change Description | Author |
---|---|---|---|
24/02/2021 | 10.0.18362.1316 | Vulnerability Report | Brandon Chong and Cao Yitian of Starlabs |
Summary Of The Vulnerability
The vulnerability is present in mfsrcsnk.dll
, which is part of the Microsoft Media Foundation framework.
An integer underflow leads to an Out-of-Bounds (OOB) Read when parsing an MP3 frame header. The crash can be triggered by navigating into a folder containing poc.mp3
.
Further Analysis
The crash occurs in mfsrcsnk.dll
:
0:000> g
(56c8.7dc4): Access violation - code c0000005 (first/second chance not available)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
Time Travel Position: B8573:0
mfsrcsnk!CMPEGFrame::DeSerializeFrameHeader+0x42:
00007ffb`2629f872 418b0e mov ecx,dword ptr [r14] ds:000001c7`29218504=????????
0:000> k
# Child-SP RetAddr Call Site
00 0000003f`dc7cde80 00007ffb`2629d50c mfsrcsnk!CMPEGFrame::DeSerializeFrameHeader+0x42
01 0000003f`dc7cdf00 00007ffb`2629cb58 mfsrcsnk!CMP3MediaSourcePlugin::ReadMPEGFrameHeader+0x78
02 0000003f`dc7cdf70 00007ffb`2629e8bc mfsrcsnk!CMP3MediaSourcePlugin::DoReadFrameHeader+0x5c
03 0000003f`dc7cdff0 00007ffb`2629f1fa mfsrcsnk!CMP3MediaSourcePlugin::ParseHeader+0x1cc
04 0000003f`dc7ce0d0 00007ffb`2629f060 mfsrcsnk!CMFMP3PropertyHandler::FeedNextBufferToPlugin+0x12e
05 0000003f`dc7ce170 00007ffb`262992e3 mfsrcsnk!CMFMP3PropertyHandler::FeedBuffersToPlugin+0x9c
06 0000003f`dc7ce230 00007ffb`262a9da4 mfsrcsnk!CMFMP3PropertyHandler::InternalInitialize+0x103
07 0000003f`dc7ce300 00007ffb`5f400df9 mfsrcsnk!CMFPropHandlerBase::Initialize+0x84
08 0000003f`dc7ce360 00007ffb`5f3fdcfb windows_storage!InitializeFileHandlerWithStream+0x175
09 0000003f`dc7ce420 00007ffb`5f43a345 windows_storage!CFileSysItemString::HandlerCreateInstance+0x2c7
0a 0000003f`dc7ce510 00007ffb`5f3de47a windows_storage!CFileSysItemString::_PropertyHandlerCreateInstance+0xad
0b 0000003f`dc7ce5c0 00007ffb`5f3ece20 windows_storage!CFileSysItemString::LoadHandler+0x1aa
0c 0000003f`dc7ce710 00007ffb`5f3c9d95 windows_storage!CFSFolder::LoadHandler+0xe0
0d 0000003f`dc7cea70 00007ffb`5f3caeca windows_storage!CFSPropertyStoreFactory::_GetFileStore+0x165
0e 0000003f`dc7ceb40 00007ffb`5f3cb042 windows_storage!CFSPropertyStoreFactory::_GetPropertyStore+0x20e
0f 0000003f`dc7cec30 00007ffb`5f3ca824 windows_storage!CFSPropertyStoreFactory::GetPropertyStore+0x22
10 0000003f`dc7cec70 00007ffb`5f3ca3cb windows_storage!CShellItem::_GetPropertyStoreWorker+0x384
11 0000003f`dc7cf1b0 00007ffb`5fd09e3b windows_storage!CShellItem::GetPropertyStore+0xdb
12 0000003f`dc7cf480 00007ff6`10d611ab SHELL32!SHGetPropertyStoreFromParsingName+0x5b
13 0000003f`dc7cf4f0 00007ff6`10d6111d harness!fuzzme+0x3b
14 0000003f`dc7cf540 00007ff6`10d615f4 harness!wmain+0x11d
15 0000003f`dc7cf7a0 00007ffb`611f7c24 harness!fuzzme+0x484
16 0000003f`dc7cf7e0 00007ffb`61aad721 KERNEL32!BaseThreadInitThunk+0x14
17 0000003f`dc7cf810 00000000`00000000 ntdll!RtlUserThreadStart+0x21
0:000> !heap -p -a @r14
address 000001c729218504 found in
_DPH_HEAP_ROOT @ 1c7290a1000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
1c7290a5d68: 1c729214000 4000 - 1c729213000 6000
00007ffb61b4462f ntdll!RtlDebugAllocateHeap+0x000000000000003f
00007ffb61af501e ntdll!RtlpAllocateHeap+0x0000000000078cce
00007ffb61a7b4bb ntdll!RtlpAllocateHeapInternal+0x0000000000000a1b
00007ffb60209da0 msvcrt!malloc+0x0000000000000070
00007ffb541aad4b MFPlat!operator new+0x0000000000000023
00007ffb541a1d76 MFPlat!MFCreateMemoryBuffer+0x0000000000000056
00007ffb2629f140 mfsrcsnk!CMFMP3PropertyHandler::FeedNextBufferToPlugin+0x0000000000000074
00007ffb2629f060 mfsrcsnk!CMFMP3PropertyHandler::FeedBuffersToPlugin+0x000000000000009c
00007ffb262992e3 mfsrcsnk!CMFMP3PropertyHandler::InternalInitialize+0x0000000000000103
00007ffb262a9da4 mfsrcsnk!CMFPropHandlerBase::Initialize+0x0000000000000084
00007ffb5f400df9 windows_storage!InitializeFileHandlerWithStream+0x0000000000000175
00007ffb5f3fdcfb windows_storage!CFileSysItemString::HandlerCreateInstance+0x00000000000002c7
00007ffb5f43a345 windows_storage!CFileSysItemString::_PropertyHandlerCreateInstance+0x00000000000000ad
00007ffb5f3de47a windows_storage!CFileSysItemString::LoadHandler+0x00000000000001aa
00007ffb5f3ece20 windows_storage!CFSFolder::LoadHandler+0x00000000000000e0
00007ffb5f3c9d95 windows_storage!CFSPropertyStoreFactory::_GetFileStore+0x0000000000000165
00007ffb5f3caeca windows_storage!CFSPropertyStoreFactory::_GetPropertyStore+0x000000000000020e
00007ffb5f3cb042 windows_storage!CFSPropertyStoreFactory::GetPropertyStore+0x0000000000000022
00007ffb5f3ca824 windows_storage!CShellItem::_GetPropertyStoreWorker+0x0000000000000384
00007ffb5f3ca3cb windows_storage!CShellItem::GetPropertyStore+0x00000000000000db
00007ffb5fd09e3b SHELL32!SHGetPropertyStoreFromParsingName+0x000000000000005b
00007ff610d611ab harness!fuzzme+0x000000000000003b
00007ff610d6111d harness!wmain+0x000000000000011d
00007ff610d615f4 harness!fuzzme+0x0000000000000484
00007ffb611f7c24 KERNEL32!BaseThreadInitThunk+0x0000000000000014
00007ffb61aad721 ntdll!RtlUserThreadStart+0x0000000000000021
@r14
points to an invalid region on the heap.
Vulnerability Analysis
At CMP3MediaSourcePlugin::ParseHeader+0x314 (mfsrcsnk.dll+0xea04)
, the function CMP3MediaSourcePlugin::DoScanForFrameHeader()
is called. This stores the value 0x2282
into the variable offset
.
// buf = 0x000001c729214000, remaining_size = 0x00000000000022e6, &offset = 0x0000003fdc7ce060
hr = CMP3MediaSourcePlugin::DoScanForFrameHeader(MPEGFrame, buf, remaining_size, &offset);
At CMP3MediaSourcePlugin::ParseHeader+0x20e (mfsrcsnk.dll+0xe8fe)
the variables remaining_size
and buf
are updated.
LABEL_29:
LODWORD(v34) = offset;
remaining_size -= offset; // 0x00000000000022e6 - 0x0000000000002282 = 0x0000000000000064
buf += offset; // 0x000001c729214000 + 0x0000000000002282 = 0x000001c729216282
goto LABEL_30;
}
At CMP3MediaSourcePlugin::ParseHeader+0x2d9 (mfsrcsnk.dll+0xe9c9)
, the function CMP3MediaSourcePlugin::DoReadFirstFrameBody()
is called.
// buf=000001c729216282, remaining_size=0000000000000064, &offset=0000003fdc7ce060
hr = CMP3MediaSourcePlugin::DoReadFirstFrameBody(MPEGFrame, buf, remaining_size, &offset);
Eventually, the function CMPEGFrame::DeSerializeFrameBody()
is called with the same arguments:
0:000> k
# Child-SP RetAddr Call Site
00 0000003f`dc7cdee8 00007ffb`2629f789 mfsrcsnk!CMPEGFrame::DeSerializeFrameBody
01 0000003f`dc7cdef0 00007ffb`2629aaa1 mfsrcsnk!CMP3MediaSourcePlugin::ReadMPEGFrameBody+0x49
02 0000003f`dc7cdf60 00007ffb`2629e9ce mfsrcsnk!CMP3MediaSourcePlugin::DoReadFirstFrameBody+0x41
0:000> r rcx, rdx, r8, r9
rcx=000001c72921bea0 rdx=000001c729216282 r8=0000000000000064 r9=0000003fdc7ce060
At CMPEGFrame::DeSerializeFrameBody+0x2fe5f (mfsrcsnk.dll+0x3f15f)
, as remaining_size
is less than required_size
, the check fails and the function immediately returns with HRESULT 0
. The value of offset
is not updated and remains 0x2282
.
if ( body_tag == 'ofnI' ) {
LODWORD(required_size) = required_size + 0x74;
if ( remaining_size < required_size ) // required_size = 0x74
goto LABEL_22;
}
LABEL_22:
CallStackScopeTrace::~CallStackScopeTrace(v13);
return hr;
}
At CMP3MediaSourcePlugin::ParseHeader+0x2f7 (mfsrcsnk.dll+0xe9eb)
, the variables remaining_size
and buf
are updated again. However, as remaining_size
is an unsigned int
, an integer underflow occurs, causing remaining_size
to store a large value. Also, buf
now points to an invalid heap region.
LODWORD(v34) = offset;
remaining_size -= offset; // 0x0000000000000064 - 0x0000000000002282 = 0x00000000ffffdde2
buf += offset; // 0x000001c729216282 + 0x0000000000002282 = 000001c729218504
At CMPEGFrame::DeSerializeFrameHeader+0x39 (mfsrcsnk.dll+0xf869)
, a check is performed. Since remaining_size
contains a large value, the check is passed. Execution flow continues, causing an OOB Read and a crash when trying to access the invalid pointer stored in buf
.
if ( remaining_size < 4 ) {
... // Irrelevant Code
}
v10 = *buf; // OOB Read
Conclusion
In the end, it was concluded that this bug was in fact not exploitable and was an information disclosure bug. This is not to make light of information disclosure bugs but it is slightly disappointing that this was not exploitable as I would definitely have liked to do further research into actually crafting an exploit poc and popping calc.exe.
Nevertheless, this does show that fuzzing does have great potential to produce viable crashes as well as even CVEs as long as the input corpus is extensive enough (and also filtered to remove any potential garbage inputs). This was a pretty fun venture as I have never really worked with windows when it comes to security before and it was intruiging trying to figure out the internal workings of windows libraries and even the windows kernel.
Afterword
This post has long been due and I do apologize for that but it has been really hectic these few months and I also haven’t been able to do much lately in this field as serving the military really is taking up a lot more time than I predicted months ago. I will try my best to finish up any posts that I had been meaning to complete and also continue writing new posts if I ever find anything interesting, but do expect the frequency of these posts to drop by a lot.
As always, it’s been a fun ride tearing my hair out analyzing the bug and its weird occasionally-not-crashing tendencies and I’m definitely happy that in the end I managed to claim a CVE for it.
But well, I suppose that’s all for now. Thanks for reading.