Video Screencast Help
Security Response

Double Free Vulnerabilities - Part 1

Created: 19 Jan 2007 08:00:00 GMT • Updated: 23 Jan 2014 18:53:25 GMT
Matthew Conover's picture
0 0 Votes
Login to vote

In light of the recent CSRSS double free bug, I wanted to providesome information on the exploitation of double frees on Windows on XPSP2 and later. Prior to XP SP2, double frees were trivial to exploit,but now the security cookie (in each heap chunk) and safe unlinkingchecks make it more difficult to exploit. So this blog entry willdiscuss the exploitability on XP SP2 and later heap implements.

Note: If you're not familiar with Windows heap terminology, pleasereview the slides from our CanSecWest 2004 heap presentation, archivedhere: http://www.cybertech.net/~sh0ksh0k/projects/winheap.

Oded Horovitz and I did not look into this topic much in ouroriginal presentation on Reliable Windows Heap Exploitation atCanSecWest 2004. Later that same year, I discussed how to defeat thesafe unlinking check at SyScan 2004, but I did not consider itsrelevance to double free vulnerabilities. In other words, thetechniques I’ll describe here are not new, but I have not seen theirapplication to exploiting double frees on XP SP2 and later publiclydocumented anywhere.

About double free vulnerabilities

A double free vulnerability is where a pointer is accidentally freedtwice. The idea is generally that the chunk is freed (and added to a FreeListfor future use). It is later allocated and used by a differentallocation before being freed again (erroneously). This gives anattacker the ability to arbitrarily set the forward/backward links inthe heap chunk. A free chunk is added to a doubly linked list of otherchunks on the same free list. These forward and backward pointers arestored within the chunk data itself (offset 0 and 4 of the chunk data).In the case of double free vulnerabilities, this means the attacker hasfull control over the FreeList.Flink and FreeList.Blink in the double freed chunk, making 4-byte overwrites trivial.

On XPSP2, however, it is no longer so trivial because the safe unlink check ensures before unlinking that:
Chunk->FreeList.Blink->Flink == &Chunk
&&
Chunk->FreeList.Flink->Blink == &Chunk

Review of technique to defeat safe unlinking

I demonstrated in the SyScan presentation that this could be evaded when the chunk is freed to a FreeList and it is the only entry on the FreeList. Let's review how safe unlinking can be defeated before we move on to double-free vulnerabilities.

How is it possible to defeat the safe unlink check? Because then the FreeList list head, which is a circular linked list, looks like this:
FreeList[SizeX].Flink = &YourChunk
FreeList[SizeX].Blink = &YourChunk
YourChunk.Blink = &FreeList[SizeX]
YourChunk.Flink = &FreeList[SizeX]

That condition is only true if the chunk is the only item on the FreeList for SizeX (that is, Chunk.Size is equal to SizeX),since Chunk is both the first and last entry (again, the list iscircular). This allows us to defeat the safe unlink check by flippingthe Flink/Blink. Now if you know that Flink is at offset 0 and Blink is at offset 4 of the LIST_ENTRY structure, you can defeat the safe unlink checking using this setup:
Set malicious Flink to &FreeList[SizeX]-4 (in other words &FreeList[SizeX-1].Blink)
Set malicious Blink to &FreeList[SizeX]+4 (in other words &FreeList[SizeX].Blink)

This works because:
YourChunk.Blink->Flink == *(*YourChunk.Blink)+0) == *(&FreeList[SizeX].Blink)
YourChunk.Flink->Blink == *(*YourChunk.Flink)+4) == *(&FreeList[SizeX].Flink)

Both of these point to YourChunk so the safe unlink check is defeated,but the unlink result will corrupt the heap. The second unlink willreturn a pointer directly into FreeLists[] (meaning your canarbitrarily corrupt the FreeLists following the second unlink).

Original flink/blink in overflowed heap chunk:
freelist[n-1] [0x003401b8] Flink 0x003401b8 Blink 0x003401b8
freelist[n] [0x003401c0] Flink 0x00341fb0 Blink 0x00341fb0
chunk [0x00341fa8] Flink 0x003401c0 Blink 0x003401c0

After modifying the flink/blink in the overflowed heap chunk:
freelist[n-1] same
freelist[n] same
chunk [0x00341fa8] Flink 0x003401bc Blink 0x003401c4

After 1st allocation following the modification (returned 0x00341fb0):
freelist[n-1] same
freelist[n] [0x003401c0] Flink 0x003401c4 Blink 0x003401bc

After 2nd allocation following the modification (returned 0x003401bc):
freelist[n-1] [0x003401b8] Flink 0x0008015c Blink 0x003401b8
freelist[n] [0x003401c0] Flink 0x003401c4 Blink 0x003401bc

After copying 0x909090909090909090 into chunk returned from the 2nd allocation:
freelist[n-1] [0x003401b8] Flink 0x0008015c Blink 0x90909090
freelist[n] [0x003401c0] Flink 0x90909090 Blink 0x90909090

So in this case, by switching the Flink/Blink we corrupted the heap and actually managed to get a pointer returned to the FreeLists[] itself, which means we can now arbitrarily modify future allocations by setting the FreeLists list heads to what we want.

To be continued in Part 2…