Posted by Mark Brand and Chris Evans, isolators of heaps
Whilst Project Zero has gained a reputation for vulnerability and exploitation research, that's not all that we do. One of the main reasons we perform this research is to provide data to defenders; and one of the things that defenders can do with this data is to devise exploit mitigations.
Sometimes, we'll take on exploit mitigations ourselves. Recently, we've been working with Adobe on Flash mitigations, and this post describes some significant mitigations have landed over the past couple of Flash versions.
Now is a good time to check your current Flash version because you really want the latest one. For example, if you're running Google Chrome, you can visit about:version to check the versions of various components. If you have Flash v18.0.0.209 (or newer), then you have the new goodies. If not, you can (on Windows) visit chrome://chrome to give the autoupdater a kick. (If you’re running an older version of Chrome, you will in fact receive a more active warning and block when encountering Flash content.)
Before we dive into the technicalities of some of the mitigations landed, it's worth reminding ourselves how the vast majority of recent Flash 0-day, 1-day and research exploits have worked:
In the above diagram, we see a heap overflow vulnerability being exploited. The attacker has performed "heap grooming", which attempts to place an object of interest after the object from which the heap overflow originates. The chosen object of interest is a Vector.<uint> buffer, which starts with a length. The desired corruption is to corrupt and increase this length. This technique was used in recent 0-days, as well as various 1-days.
And in this diagram, we see a use-after-free vulnerability being exploited. The attacker has caused a heap chunk to be freed whilst it is still referenced. The attacker then allocates a Vector.<uint> into this freed heap chunk and the subsequent use-after-free writes over the Vector.<uint> length with a larger value. This technique was used in at least two of the recent Hacking Team 0-days.
The commonality between both cases is an abuse of a corruption of a Vector.<uint> buffer object's length, which has been the go-to method for Flash exploitation for a while. Aside from 0-days and 1-days, Project Zero's own research has used this technique. For example, when working on the new exploit primitive "parallel thread corruption", we used Vector.<uint>. And why wouldn't we? There's no point in doing something complicated when something simple exists to solve the problem. And it is exactly because the Vector.<uint> primitive is so simple and powerful that it needs to be addressed.
Mitigation: Vector.<uint> buffer heap partitioning
Heap partitioning is a technique that isolates different types of objects on the heap from one another. Chrome uses heap partitioning extensively, and it has become a common defensive technique in multiple browsers. We have now introduced this technology into Flash. In its initial form, Vector.<uint> buffer objects (and other very similar objects) are now isolated from the general Flash heap, because we put them in the standard system heap:
We have now defended the integrity of Vector.<uint> buffers from both heap overflow and use-after-free attacks! In the case of a heap overflow within the Flash heap, the Vector.<uint> buffer objects are simply not there to corrupt. In the case of the use-after-free, when the attacker tries to allocate the Vector.<uint> buffer to occupy the free heap chunk of interest, it will not end up being allocated there because it lives in a different heap.
It's worth noting that this defense is much more powerful in a 64-bit build of Flash, because of address space limitations of 32-bit processes. Now is a good time to upgrade to a 64-bit browser and Flash. For example, if you're using Chrome on Windows 7 x64 (or newer), you might be running a 32-bit browser on a 64-bit capable system. To check, visit chrome://chrome and look for the string "64-bit" -- its presence or absence will indicate whether you have the 32-bit or 64-bit build. Further details on the advantages of the 64-bit Windows build are covered on the Chromium blog.
Mitigation: stronger randomization for the Flash heap
One day, while browsing the heap mappings for a Flash process on x64 Windows 7, we noticed a strong determinism in the position of the Flash heap. We filed bug 276, which is now fixed. Beyond fixing the immediate issue of the Flash heap being at a predictable location, the patch we provided had useful side effects to hamper exploitation:
- Large allocations (> 1MB) are also randomized better than they used to be. This means, for example, that this previous Project Zero work, which relied on 1GB+ allocations being packed in predictable locations, is no longer a viable exploitation technique.
- On 64-bit systems, the Flash heap will likely end up very far away from other memory mappings. This provides some interesting properties. For example, in the same exploit, a buffer length is corrupted and the buffer is then used to read function pointers in the PLT section of the Flash library. A buffer length is typically 32-bit, so the range of the corruption that can be effected by an errant buffer might be something like 2^32 * sizeof(unsigned int), or 16GB. With this new mitigation in place, the binary sections are unlikely to be close enough to the Flash heap for buffer corruptions to be able to reach them. The attacker would need an additional level of indirection.
To look at the runtime effect of the combination of the above two mitigations, we can run a simple Flash program which allocates a large Vector.<uint> and a large ByteArray:
_bigVec = new Vector.<uint>;
_bigVec.length = ((512 * 1024 * 1024) - 500) / 4;
_bigVec[0] = 0xf2f2f2f2;
_bigArr = new ByteArray();
_bigArr.length = 512 * 1024 * 1024;
_bigArr[0] = 0xf1;
And then look at the resulting process mappings; Chrome Linux x64 in this case:
2d7ef200000-2d7ef209000 rw-p 00000000 00:00 0
35001005000-35022005000 rw-p 00000000 00:00 0 // Isolated 512MB ByteArray buffer.
a7026000000-a7026100000 rw-p 00000000 00:00 0
[...]
35001005000-35022005000 rw-p 00000000 00:00 0 // Isolated 512MB ByteArray buffer.
a7026000000-a7026100000 rw-p 00000000 00:00 0
[...]
11655c57f000-11657c87f000 rw-p 00000000 00:00 0 // tcmalloc heap; Vector.<uint>
// buffer at 0x11655c6ae000
[...]
3d3c8715f000-3d3c8716f000 r-xp 00000000 00:00 0 // Main Flash heap mappings (x3)
3d3c8716f000-3d3c8760b000 rw-p 00000000 00:00 0
3d3c8760b000-3d3c87f4b000 ---p 00000000 00:00 0
3d3c8716f000-3d3c8760b000 rw-p 00000000 00:00 0
3d3c8760b000-3d3c87f4b000 ---p 00000000 00:00 0
This new heap partitioning is currently enabled for the Pepper plug-in versions of Flash, so you can see it in action right away in Google Chrome (all operating systems). Adobe are planning to extend the partitioning to cover non-Pepper plug-ins in August.
Mitigation: Vector.<*> length validation
In the same Flash patch, there are not one, but two mitigations for Vector length corruptions! This second mitigation was authored by Adobe. It works by storing Vector lengths alongside a validation secret. If the attacker corrupts a length, they do not know what value to set the secret to, and the resulting mismatch results in a runtime abort. This mitigation is present across all Flash builds as of 18.0.0.209.
It may sound strange to have two mitigations for one exploitation primitive, but the mitigations actually complement each other very nicely. Heap partitioning is very effective in 64-bit builds, but address space limitations can cause a bit of a squeeze in 32-bit. Perhaps the best example of this is our blog post on bug 229, which is a bug that only has impact on 32-bit builds. The primitive provided by the bug is the ability to write at any absolute address in the 32-bit address space. Obviously, this primitive crosses partitions and a heap spray on 32-bit can make a chosen address likely to be mapped. Therefore, attempts to exploit this bug on 32-bit may sidestep partitioning but get hampered by the length validation.
Furthermore, the heap partitioning does not cover Vector.<Object> buffers due to the way garbage collection works. The length validation covers this and all types of Vector. Finally, we note that Vector.<uint> buffers are (currently) in the system heap as opposed to a distinct partition. So even though most allocations in the Flash process occur in the Flash heap, there’s still the possibility that a memory corruption in the system heap could corrupt a Vector.<uint> buffer and the length validation helps here.
The future
We believe we've contributed a strong step forward in Flash security, but we're very far from finished. For every mitigation landed by defenders, attackers will attempt to devise a counter-mitigation. It's a cat-and-mouse-game, but we'll be looking out for attackers' attempts to adapt, and devising further mitigations based on what we see. Perhaps more importantly, we're also devising a next level of defenses based on what we expect we might see. Our partitioning mitigation is far from finished. We’ll be analyzing object types to see what else might benefit from partitioning, and moving forward incrementally.
On top of this, we're continuing to help lock down the various browser sandboxes, and working on compiler-based mitigations. As always, we'll fully share our work when we have something interesting to show.
We'd like to thank Adobe for working with us.
0 Comments:
Post a Comment