I am not usually known for finding memory corruption vulnerabilities, mainly because I don’t go looking for them. Still I know my way around and so I knew the challenges I would face trying to come up with a suitable mitigation bypass entry. I realised that about the only way of having a successful entry would be to take a difficult to exploit memory corruption vulnerability and try and find a way of turning that into reliable code execution.
For that reason I settled on investigating the exploitation of a memory overwrite where the only value you could write was the number 0. Converting a 0 overwrite of this sort, while not impossible to exploit, certainly presents some challenges. I also stated that I could not disclose the existing contents of memory. If you have an information disclosure vulnerability then it is generally game over anyway, so I was confident that would not pass for a winning entry.
ActiveX and COM
The attack vector for the mitigation bypass was safe-scriptable COM objects. As COM is a general technology, not limited to safe-scripted environments such as Internet Explorer, there are many unsafe objects which could be abused if they were allowed to be created. To prevent this, hosts, such as Internet Explorer, use two mechanisms to determine whether an object is safe for being used in the host environment, Category IDs and the IObjectSafety interface. The Category IDs, CATID_SafeForScripting and CATID_SafeForInitializing can be added to a COM object registration to indicate to a COM host that the object is safe for either scripting or initialisation. These are static indicators, and are not particularly of interest.
Things get more interesting with the IObjectSafety interface which is implemented by the COM object. The host can call the GetInterfaceSafetyOptions method to determine whether a COM object is safe to script or initialise (of course this means that the object must have already been created). The interface also has a secondary purpose; once a host has determined that an object is safe it can call the SetInterfaceSafetyOptions method to tell the object how safe it needs to be.
This method has a particular implication; it allows COM objects to be written in a generic way with potentially dangerous functionality (such as arbitrary script code execution) and then secured at runtime by disabling the unsafe functions. The typical way this is implemented is by setting flags within the object's memory to indicate the security of the object. This is the attack vector chosen. If we have a suitable memory corruption vulnerability it might be possible to change these security flags to convert a secure object back to an insecure one and use that to circumvent in-place mitigations.
A related topic is the setting of an object's site. A Site is normally a reference to the hosting environment for the COM object, such as the OLE container or hosting HTML document. This makes a number of security related functions possible, such as enforcing the same-origin policy for COM objects in a web page (through querying for the IHTMLDocument2 interface and reading the URL property), zone determination or accessing the host security manager. Depending on what we attack we might need to deal with the Site as well.
The important point of all this is by default there are many objects which are unsafe until certain flags are stored within the memory allocated for the object. Therefore the unsafe state of these flags is the value 0, where as the safe state is non-zero. This means that if we have got a 0 overwrite vulnerability we can reset the security flags back to the unsafe state and exploit the unsafe functionality of the COM object.
To demonstrate an attack against scriptable COM objects a suitable object is needed. It must meet some set of criteria to allow us to use the memory corruption vulnerability to bypass mitigations. I determined that the criteria were:
- The object must be creatable in common COM hosts without significant security issues such as being blocked by policy or site locking
- The object must be available on default Windows installations or be extremely common
- The object must do something of benefit to an attacker when insecure, but not expose that functionality when secure (otherwise it would just be a security vulnerability)
- It must be relatively trivial to convert from secure to insecure through a minimal number of zero memory overwrites
The COM objects chosen for the demonstration are implemented by the MSXML libraries. Windows 8.1 comes with versions 3 and 6 of the MSXML library installed by default. They are pretty much considered de-facto secure as without them some websites would break; therefore there are no issues with site-locking or blacklisting. They can even be created in the immersive version of IE without issue. They also have some significant functionality when insecure, namely the ability to circumvent same-origin policy and also to execute fully-privileged scripts within the context of XSL transformation.
So MSXML meets the first three criteria, but what about the 4th? Many of the objects that MSXML exposes implement the IObjectSafety interface which is the mechanism through which safety is enabled as shown above. The object also supports the INTERFACE_USES_SECURITY_MANAGER flag which means that the object will utilise the security manager from the hosted site to make some trust decisions. Through reverse engineering the safe objects such as DOMDocument and XMLHTTP, it can be seen that they all contain the COMSafeControlRoot structure, which is used to implement the IObjectSafety and security manager features. In MSXML3 this consists of 6 fields, in the default insecure version these values are all NULL, while in a secure version they contain pointers to site objects and security managers as well as the current security flags set through SetInterfaceSafetyOptions. The rough outline of this structure is shown below:
Through inspection, I found that of the 6 values in memory only two were important when it came to bypassing the security mechanisms. This was a pointer to the host security manager at offset 4 and the security flags at offset 20. Crucially these can be reverted back to NULL without causing any other significant effect on the object’s functionality. This means that a very restricted memory corruption could achieve the desired effect, namely our overwrite with zero.
Finding an Object in Memory
The biggest issue with this technique is that whilst it would be easy enough to modify an object in memory to disable the security without an information disclosure vulnerability, we would not know where it was. If you had an information disclosure vulnerability you probably would not need to use this technique at all.
The bypass must be able to guess the location of a valid object in memory and attack it blind. The design of typical scriptable COM hosts come in handy here to achieve this goal.
- They usually allow you to create an arbitrary number of new objects, this allows for the heap to be flooded with object instances
- The allocation of COM objects is up to the COM library to implement; therefore it might not be using best practice or it might disable security mitigations
- The scripting ability allows for executing specific sequences of operations to improve reliable allocation patterns
In the general case this makes it a lot easier to use a heap flood technique to generate a reliable pattern of objects on the heap and of a large enough size to guess the location of an object. If a regular pattern of objects can be achieved we can use an arbitrary overwrite to modify values in memory through a guessed location and then find the insecure object to execute our code.
There are some issues with the heap improvements in Windows 8. For a start there is a new mitigation called Low Fragmentation Heap Randomisation. The Low Fragmentation Heap (LFH) is a special memory heap used for small allocations to reduce the amount of memory fragmentation that occurs during allocation and freeing of memory. In Windows 8 the order of what blocks is allocated has a random element to it. This makes it more difficult to lay out guessable patterns of allocations.
At least once you start allocating 1000s of objects it is still possible to find some level of reliability for allocations. However MSXML3 provides an ideal case, presumably for legacy reasons when running on a multi-processor system it creates its own heap passing the HEAP_NO_SERIALIZE flag. This means that the LFH is disabled which also disables some of the heap improvements in Windows 8. This makes the heap flooding considerably more reliable.
The targeted COM object in that library is MSXML2.XMLHTTP.3.0. This is because this object has a considerably smaller heap footprint than DOMDocument which would be the more obvious choice. As long as the object is opened you can read the requestXML property (even without sending the request) to get a DOMDocument object. This document inherits the security settings of the parent XMLHTTP object which allows us to modify XMLHTTP and then use that to execute arbitrary script code.
To lay out the heap the provided PoC creates 40,000 instances of XMLHTTP and stores them in an array. Each instance also has the ‘open’ method called on it and a request header set to increase the allocation size for a single object. This results in a repeating 8192 byte pattern of objects being created in memory which looks similar to the following:
The actual code was quite simple:
Once the heap was flooded the next step was to write the 0 values to a guessed address. The address was chosen empirically, and for the proof-of-concept the overwrite was actually performed using a custom control rather than a real memory corruption vulnerability. By guessing the base address of an object and writing 0s to offsets 4 and 20 we will have disabled the security on one XMLHTTP object, we just need to find which one. For that, the proof-of-concept just enumerated all allocated objects trying each one in turn with a XSL document with an msxsl:script tag containing JScript to start notepad. If the object is still secure then this process will throw an exception, if not we succeeded, notepad has been executed and we can stop looking.
Real World Zero Overwrites
Of course this entire bypass is predicated on finding a vulnerability which allows you to do an arbitrary overwrite with a 0. How likely is that in the real world? Well honestly I can not give any figures but don't forget that 0 is the typical default state for values, so any code which tries to initialize a value under an attackers control will probably set it to zero.
A good example is COM itself. Every COM object must implement the IUnknown interface, the first function QueryInterface is used to convert the object to different interface types. It takes a pointer to the IID and a pointer to a pointer for the returned interface, assuming it supports the required interface. It is recommended that if the object doesn't support the interface it should ensure the outbound pointer is set to NULL before returning.
If you've already guessed the location of a COM object you might only be a V-Table dereference away from your coveted arbitrary zero overwrite.
Obviously this particular example has limitations. It only worked reliably in 32 bit versions of IE as heap flooding is very difficult to do in a reliable way on 64 bit. Of course if you combined this technique with a memory disclosure vulnerability you can achieve code execution without needing to control EIP.
The technique is more general than just COM objects in IE. Any structure in a program which has both safe and unsafe functionality is a suitable target. The PoC was necessary to demonstrate the potential. It is interesting that techniques like this are subject to convergent discovery, I wasn't the only person to stumble upon a similar idea, the only reason it is an issue now is the easy routes of exploitation have been closed.