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).