The previous posts of this blog series have been about stack based buffer overflows.
With this post, I want to move on to bugs that involve dynamic memory management.
Since there are not that many publicly documented arbitrary free vulnerabilities in prominent software products, I thought it would be worth sharing this one.
Introduction
The Transport Neutral Encapsulation Format (TNEF) is an e-mail attachment format developed by Microsoft.
It can be used to represent complicated messages and attachments, consisting of many different files and file types, as a flattened stream.
Microsoft does not provide an open-source reference implementation. Instead, they provide an official documentation,
so most anti-virus vendors write their own TNEF parser.
The bug presented in this post occurs when parsing the properties of an attachment.
Getting Into the Details
Section 2.1.3.4 of the TNEF documentation describes the so-called MsgPropertyList.
The following definitions are relevant for this discussion (slightly simplified).
MsgPropertyList = MsgPropertyCount *MsgPropertyValue
MsgPropertyCount = UINT32
MsgPropertyValue = MsgPropertyTag MsgPropertyData
MsgPropertyTag = MsgPropertyType MsgPropertyId [NamedPropSpec]
//NamedPropSpec is optional, it must be present if MsgPropertyId >= 0x8000
//Definition of MsgPropertyData omitted, because irrelevant
MsgPropertyType = UINT16 (simplified)
MsgPropertyId = UINT16
NamedPropSpec = PropNameSpace PropIDType PropMap
PropNameSpace = 16 byte GUID
PropIDType = IDTypeNumber / IDTypeString
IDTypeNumber = %x00.00.00.00 (little-endian 32-bit 0x0)
IDTypeString = %x01.00.00.00 (little-endian 32-bit 0x1)
PropMap = PropMapID / PropMapString
PropMapID = UINT32
PropMapString = UINT32 *UINT16 %x00.00 [PropMapPad]
Let us look at how MsgPropertyList
is parsed.
char TNEF::ReadMsgPropertyList() {
MsgPropertyValue msgpropvalue; //constructor call here
//some init stuff omitted
if (this->currentoffset + 4 > this->endoffset) { return 0; }
else {
uint32_t MsgPropertyCount = *(uint32_t*)(&this->inputdata[this->currentoffset]);
this->currentoffset += 4;
this->MsgPropertyCount = MsgPropertyCount;
for (int32_t processedMsgProperties = 0;
this->currentoffset < this->endoffset && processedMsgProperties < this->MsgPropertyCount;
processedMsgProperties++) {
int32_t readbytes = msgpropvalue.Read(&this->inputdata[this->currentoffset],
this->endoffset - this->currentoffset);
if (readbytes == -1) { return 0; }
irrelevant_processing(&this->dword14, &msgpropvalue);
msgpropvalue.Cleanup();
this->currentoffset += readbytes;
}
return 1;
}
//destructor call on msgpropvalue here
}
This is pretty much as expected. First, MsgPropertyCount
is read from the input.
Then, a loop traverses the list of MsgPropertyValue
s.
Note that the same object msgpropvalue
is reused for every new MsgPropertyValue
that is read from the list.
Let us dig a little deeper and try to understand how a single MsgPropertyValue
is parsed.
In fact, it suffices to see how MsgPropertyTag
is parsed, so we will focus only on this part.
int32_t MsgPropertyValue::Read(char *inputdata, int32_t remainingbytes) {
//omitted some init stuff
if (remainingbytes < 4) { return -1; }
this->msgpropertytype = *(int16_t *)(inputdata);
this->msgpropertyid = *(int16_t *)(inputdata+2);
int32_t readbytes = 4;
if (this->namedpropspec_present() == 1) {
if (remainingbytes < 20) { return -1; }
memcpy(&this->guid[0], inputdata+4, 16);
this->currentguid = &this->guid[0];
if (remainingbytes < 24) { return -1; }
uint32_t propidtype = *(uint32_t *)(inputdata + 20);
this->propidtype = propidtype;
if (propidtype != IDTypeNumber) { // IDTypeNumber==0
if (propidtype != IDTypeString || remainingbytes < 28) { return -1; } // IDTypeString==1
size_t propmapstring_size = *(size_t*)(inputdata + 24);
this->propmapstring_size = propmapstring_size;
if ((int32_t)(propmapstring_size + 28) > remainingbytes) { return -1; }
char *propmapstringbuffer = (char*) malloc(propmapstring_size);
this->PropMap = propmapstringbuffer; //PropMap holds the pointer to the string buffer
if (!propmapstringbuffer) { return -1; }
memmove(propmapstringbuffer, inputdata + 28, propmapstring_size);
readbytes = this->propmapstring_size + 28;
if (this->propmapstring_size % 4) { readbytes += 4 - this->propmapstring_size % 4; }
}
else {
if (remainingbytes < 28) { return -1; }
this->PropMap = *(uint32_t*)(inputdata + 24); //PropMap holds the uint32_t PropMapID
readbytes = 28;
}
}
//rest omitted
return readbytes;
}
For the most part, this seems to be pretty straightforward, too. Note, however, that the field PropMap
does not seem to enjoy real type safety.
In particular, the field is used to store a pointer to a dynamically allocated buffer in the first branch (PropMap
is an IDTypeString)
and a 4-byte integer in the second branch (PropMap
is an IDTypeNumber).
Recall that the object msgpropvalue
from TNEF::ReadMsgPropertyList
(see above) is reused for every new MsgPropertyValue
that occurs in the list. As there are dynamic memory allocations in MsgPropertyValue::Read
, it is necessary
to free the allocated memory after every iteration in order to avoid memory leaks. Therefore, MsgPropertyValue::Cleanup
is called at the end of every loop iteration. Additionally, and this turns out to be crucial, the function is called in the destructor of MsgPropertyValue
.
void MsgPropertyValue::Cleanup() {
//other object cleanup omitted
if (this->currentguid && this->propidtype == IDTypeString && this->PropMap) {
free(this->PropMap);
this->PropMap = 0;
}
}
So far, so good. Now, we exploit the missing type safety of the field PropMap
to call free(x)
for an attacker chosen x
.
The control flow is as follows.
- An
MsgPropertyList
is read via TNEF::ReadMsgPropertyListField
.
- The first call to
msgpropvalue.Read()
sets propidtype
to IDTypeNumber, and PropMap
to an arbitrary value x
via a NamedPropSpec
with PropMap
of type IDTypeNumber.
- The next call to
msgpropvalue.Read()
sets propidtype
to IDTypeString and returns -1
because remainingbytes
is too small.
- Now
TNEF::ReadMsgPropertyList
reads the error and returns, too. This calls the destructor on msgpropvalue
, which in turn calls msgpropvalue.Cleanup()
.
- The fields
currentguid
and PropMap
both hold a non-zero value. Moreover, propidtype
is still IDTypeString.
free(x)
Triggering the Bug
The outlined control flow describes a class of MsgPropertyList
s that will trigger the bug.
We embed such an MsgPropertyList
into a TNEF stream and feed the engine with it,
setting a breakpoint just before the call to free
in the function MsgPropertyValue::Cleanup
.
0:000> bu fm4av+0x2F157
0:000> g
Breakpoint 0 hit
eax=deadbeef ebx=0057dbe8 ecx=002ae6e0 edx=00000000 esi=0057dbe8 edi=002ae6e0
eip=6ef9f157 esp=002ae694 ebp=002ae6c4 iopl=0 nv up ei ng nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000282
0:000> u
fm4av!fmOpenFileW+0x161f7:
6ef9f157 50 push eax
6ef9f158 e8ec520000 call free (6efa4449)
6ef9f15d 83c404 add esp,4
6ef9f160 c7470c00000000 mov dword ptr [edi+0Ch],0
6ef9f167 5f pop edi
6ef9f168 5e pop esi
6ef9f169 5b pop ebx
6ef9f16a c3 ret
Note that the value of eax
is completely controllable by the attacker,
since it is read directly form the input file.
Attacker Control and Exploitation
The attacker can free a completely arbitrary pointer without any restriction. Moreover, the engine runs unsandboxed and as NT Authority\SYSTEM
.
Hence, this bug is most likely exploitable for remote code execution as NT Authority\SYSTEM
, the only obstacle being ASLR.
Conclusion
We have seen an arbitrary free vulnerability that was the result from missing type safety on PropMap
.
Relying on a variable t
to always reflect the correct type of another variable v
is obviously dangerous,
especially if the type of v
changes frequently.
In particular, there is the danger of v
and t
getting out of sync, which is exactly what happened here.
Do you have any comments, feedback, doubts, or complaints?
I would love to hear them. You can find my e-mail address on the about page.
Timeline of Disclosure
- 2016-11-11 - Discovery
- 2016-11-11 - Report
- 2016-11-14 - “raised up to our development team” and “we will get back to you with a progress update”
- 2016-11-28 - “as for the use-after-free vulnerability, our development team managed to identify the source of the issue and is currently working on a fix to be implemented”
- 2016-12-09 - “fixes for the issues with [TNEF] you reported have been tested and ready to be deployed.
[…] However, the development has requested that the database be released when the fixes for the [other] issue has been included as well."
- 2016-12-15 - “the development team has decided to release the fixes for [the TNEF] issues you reported to us while still working on a fix for the issues with […]. […] The new database has been released yesterday”
- 2017-02-28 - Bug bounty paid
Thanks & Acknowledgements
I want to thank the F-Secure team for fixing the bug. In addition, I want to thank Calvin Gan for providing me with regular status updates.
Bonus
The TNEF parser calls the function uint32_t read_input(uint8_t *inputdata)
several times
to read from the input data buffer. The function looks as follows.
read_input proc near
//inputdata= dword ptr 8
push ebp
mov ebp, esp
mov ecx, [ebp+inputdata]
push ebx
mov al, [ecx]
mov dl, [ecx+1]
mov byte ptr [ebp+inputdata+3], al
mov al, [ecx+2]
mov cl, [ecx+3]
movzx ebx, al
push esi
push edi
movzx esi, cl
mov edi, ebx
shl edi, 8
or edi, esi
and edi, 0FF00h
mov eax, esi
mov ecx, ebx
shr ecx, 8
shl eax, 10h
or edi, eax
movzx eax, byte ptr [ebp+inputdata+3]
movzx edx, dl
or edi, edx
and ecx, 0FF00h
and esi, 0FF0000h
or ecx, esi
and edx, 0FF00h
shl edi, 8
or ecx, edx
shr ecx, 8
or edi, ecx
and ebx, 0FF00h
or edi, ebx
or eax, edi
pop edi
pop esi
pop ebx
pop ebp
retn
I leave it as an exercise for the interested reader to figure out what this function does.
I am also happy to discuss any comments or ideas on the function (my e-mail address can be found here).