Saturday, September 27, 2014

Exile for the BBC Micro; some elegant solutions

[Prelude: sorry, this has nothing to do with security whatsoever. Feel free to bail now if you're not interested in a classic 1980's game, and rest assured that non-security posts to this blog will remain extremely rare.]

The BBC Micro game Exile, released in 1988, has a realistic claim for the best game ever. I lost months of my youth to this game. I also lost a fair few days recently re-playing the game under emulation in 2014!

What the authors were able to do with just 32KB (for everything, including video RAM) was amazing. The art of coding in this way has simply been lost. The game features:

  • An enormous map featuring fairly open-ended exploration.
  • A full physics engine (gravity, momentum, conservation of momentum, buoyancy, friction).
  • Dozens of fiendish puzzles, characters and objects, with many interactions between entities.
There's even a great disassembly online. I was quite surprised to see that the game really is powered by real 6502 opcodes, and not unicorn tears.

The claim for "best game ever" isn't just about packing so much into such a small resource. Completing the game, even if you know what you're doing, is hours of immersive play that alternates between solving very varied puzzles and arcade-like blowing stuff up. Given how free-form the game is, there are also different solutions and orderings to to the game, so you can put your own personal spin on things.

If you want to see what all the fuss is about, the best emulator is probably B-em (part of a webring, remember those??) and the Exile game image can be readily found. And do feel free to stop reading to avoid the spoilers that now follow.

There seem to be two solutions published on the web. Unfortunately, both have triggered my OCD. Both have solutions for some of the more interesting problems that rely on abusing the limits and corner cases of the game engine, such as:
  • Using the built-in viewport scrolling to sneak around with the viewport scrolled to the extreme so that an enemy or obstacle does not "see" the player.
  • Abusing the fact that the physics engine "forgets" objects that are offscreen, causing corner-case and clearly unintentional behavior. (Give the poor game a break, it's trying to fit everything into 32KB!)
  • Proposing solutions with low reliability.
More significantly, these problems have such beautifully elegant solutions that once you see them, it's clear that you've worked out the authors' original intent. So without further ado, here is a small collection of videos that illustrates some elegant solutions as well as an easter egg:

Getting the alien weapon


The game features a tricky-to-get alien weapon! In fact you can get it from two different places. Generally, you can feed different types of imps different "gifts" and then they might later throw you a gift in return. In this video, these cyan imps will accept a blue mushroom. Later in the game, dark blue imps accept piranhas. I had found this later exchange, but this earlier exchange was a complete surprise to me -- I only noticed it reading the disassembly referenced above.
It's a real boon to get such a powerful weapon earlier in the game. It never runs out of energy and it has good destructive power for some obstacles that are otherwise annoying. Just watch out you don't burn yourself and that you don't blow the weapon up, it's destructible.

Getting the first coronium rocks out of the alcove


There's no need to try and force the rock past the blowing bush. Speedy throwing and viewport scrolling are not necessary. There's a simple, elegant sequence that will rescue it reliably and without dubiousness.

Blowing open the rune door

The first two-thirds or so of the game are building up towards opening a very important door into the bad guy's lair. This door is blown open with a nuclear explosion between two radioactive rocks. Given the importance of the door, it's not surprising that the final puzzles towards opening it have beautiful solutions.
Both published solutions transport radioactive rocks via a route that is clearly not supposed to be an option, and the route only works on account of abusing game engine quirks. Tut tut! Here's a less hacky way of getting the required rocks, in three parts:




There's a lot going on here:
  • In part #1, the use of the maggot to "wake up" the nest of green slimes is fun. Note that this doesn't always work! Game design bug? This is a very busy area of the map and the game engine often decides there's too much on-screen to spawn creatures from the nest. I lost a day stepping through 6502 assembler to understand this.
  • Then, the green slimes appear attracted to sound. So we made some noise!
  • The use of buoyancy to avoid the sucking bush is the first and only significant usage in a puzzle. Wonderful.
  • In part #2 and #3, the presence of the big fish prevents the (very dangerous) piranhas from coming out the nest and ruining your day.
  • In part #3, the piranha is actually immune to damage from acid drops -- I believe the only creature in the game that has this trait. (You can even check the disassembly :-)
Getting the mushroom immunity pill

Again, this puzzle involves blowing a door open with radioactive rocks. The published solutions suggest all sorts of hacks here, but there's a really neat solution with the "blaster" weapon that has been recently collected at this stage in the game. Previous weapons were projectile based but this one is force based and it can be used variously: at a distance to gently deviate the course of an acid drop, and then at close range to reliably launch a rock past a problem area.


Happy exploring.

Thursday, September 25, 2014

Using ASAN as a protection

AddressSanitizer, or ASAN, is an excellent tool for detecting subtle memory errors at runtime in C / C++ programs. It is now a productionized option in both the clang and gcc compilers, and has assisted in uncovering literally thousands of security bugs.

ASAN works by instrumenting compiled code with careful detections for runtime errors. It is primarily a detection tool. But what if we attempted to use is as a tool for protection?

The case for using ASAN-compiled software as a protection is an interesting one. Some of the most severe vulnerabilities are memory corruptions used to completely compromise a victim's machine. This is particularly the case for a web browser. If an ASAN-compiled build can help defend against these bugs, perhaps it has value to some users? An ASAN build is slower enough that no production software is likely to ship compiled with ASAN. But the slow down is not so bad that a particularly paranoid user wouldn't be able to easily accept it on a fast machine.

With that trade-off in mind, let's explore: does ASAN actually provide protection? To answer that, let's break memory corruption down into common vulnerability classes:

1. Linear buffer overflow (heap, stack, BSS, etc.)
A linear buffer overflow is one where every byte past the end of a buffer is written in sequence, up to some end point (example). For example, a memcpy() or strcpy() based overflow is linear. Because of the way ASAN works, I believe it will always catch a linear buffer overflow. It uses a default "redzone" of at least 16 bytes, i.e. touching _any_ address within 16 bytes of a valid buffer will halt the program with an error. Under ASAN, a linear buffer overflow condition will always hit the redzone.
This is great news because linear buffer overflows are one of the more common types of security bugs, and they are quite serious, affording the attacker a lot of control in corrupting program state.

2. Non-linear buffer overflow
A non-linear buffer overflow is one where data is written at some specific (but often attacker-controlled) out-of-bounds offset relative to a buffer (example). These bugs can be extremely powerful. Unfortunately, because of their power, they are both favored by attackers and also not stopped by ASAN if the attacker knows they are targeting an ASAN build. Example C program:

int main()
{
  char* p = malloc(16);
  char* p2 = malloc(16);
  printf("p, p2: %p, %p\n", p, p2);
  p2[31] = '\0';
}

Compile it with ASAN (clang -fsanitize=address) and then run it and no error will be detected. The bad dereference "jumps over" the redzone to corrupt p2 via pointer p.

3. Use-after-free / double-free
ASAN does detect use-after-frees very reliably in the conditions that matter for current use cases: normal usage, and under fuzzing. However, if the attacker is specifically targeting an exploit against an ASAN build, they can pull tricks to still attempt the exploit. By churning the memory allocator hard (as is trivially possible with JavaScript), the condition can be hidden. Example C program:

int main()
{
   int n = 257 * 1024 * 1024;
   char* p2;
   char* p = malloc(1024);
   printf("p: %p\n", p);
   free(p);
   while (n) {
     p2 = malloc(1024);
     if (p2 == p) printf("reused!!\n");
     free(p2);
     n -= 1024;
   }
   n = 30 * 1024 * 1024;
   while (n) {
     p2 = malloc(1024);
     if (p2 == p) printf("reused!!\n");
     n -= 1024;
   }
   p[0] = 'A';
}

The bad reference is not trapped with default ASAN values. The default values can be changed such that the bad reference is trapped:

ASAN_OPTIONS=quarantine_size=4294967295 ./a.out

It's a shame that setting this value to "unlimited" may not be possible due to a probable integer truncation in parameter parsing, see how this behaves differently:

ASAN_OPTIONS=quarantine_size=4294967296 ./a.out

4. Uninitialized value
Uninitialized values are harder to categorize. The impact varies drastically depending on where the uninitialized value is a pointer or an integer. For example, for an uninitialized pointer, effects similar to "non-linear buffer overflow" might even apply. Or if the uninitialized value is a copy length then perhaps it's more similar to "linear buffer overflow".
Or, if it's an uninitialized raw function pointer, that's a bigger problem. Indirect jumps are not checked. The behavior of the following ASAN-compiled program is instructive (run it in the debugger):

void subfunc1()
{
  unsigned long long blah = 0x0000414141414141ull;
}

void subfunc2()
{
  int (*funcptr)(void);
  funcptr();
}

int main()
{
  subfunc1();
  subfunc2();
}

If the uninitialized value is a pointer to a C++ class then similar (indirect) problems apply.

5. Bad cast
The effects of a bad cast are fairly varied! Perhaps the bad cast involves mistakenly using an integer value as a pointer. In this instance, effects similar to "non-linear buffer overflow" might be achievable. Or perhaps if a pointer for a C++ object is expected, but it is mistaken with a pointer to a raw buffer, then a bad vtable gets used, leading to program flow subversion. One final C++ example to illustrate this. Run under ASAN to observe a raw crash trying to read a vtable entry from 0x0000414141414141:

class A
{
public:
  long long val;
};

class B
{
public:
  virtual void vfunc() {};
};

int main()
{
  class A a;
  a.val = 0x0000414141414141ull;
  class B* pb = (class B*) &a;
  pb->vfunc();
}


Safer ASAN?
There's certainly scope for a safer variant of ASAN, specifically designed to provide safety rather than detection. It would be based on various changes:

  • Change the dereference check from "is this dereference address ok?" to "is this address in bounds for this specific pointer?". This takes care of the nasty "non-linear buffer overflow" as well as some of the worst effects of bad casts. This is not an easy change.
  • Initialize more variables: pointer values on the stack and heap. (This is not as easy as it sounds, particularly for the heap case, where the casting operator may become a point of action.)
  • Make the quarantine size for use-after-free unlimited. This burns a lot of memory, of course, but may be acceptable if fully unused pages are returned to the system with madvise() or even a crazy remap_file_pages() trick.


Remaining risks

Of course, even a "safer ASAN" build would not be bullet-proof. Taking the specific case of an safer-ASAN compiled Chromium, there would still be additional attacks possible:

  • Plug-ins. Many plug-ins are closed source and therefore cannot be replaced with ASANified versions. The safer build of Chromium would have plug-ins disabled: --disable-plugins or even at compile time.
  • Native attack surfaces called by the browser. For example, what happens when the browser encounters a web font. It'll probably get passed to a system library which parses this dangerous format using native code. In extreme cases, such as older Chromium on Windows, fonts were parsed in the kernel(!). --disable-remote-fonts, probably other flags.
  • Native attack surfaces triggerable by the browser. Less obviously, there can be operating system mechanisms that kick in simply because a file is downloaded or appears on disk. Anti-virus is notoriously buggy in this regard.
  • The v8 JIT engine. Any logic error in the JIT engine resulting in the emission of bad opcode sequences, or sequences with buggy bounds checks, are pretty toxic.
  • Pure logic vulnerabilities. UXSS vulnerabilities will remain unmitigated. In extremely rare but spectacular cases, unsandboxed code execute has been achieved without the need for memory corruption at all.

That all said, a stock ASAN build -- and even more so a hypothetical safer-ASAN build -- provide significant mitigation potential against memory corruption vulnerabilities. One measure of how strong a mitigation is, is whether is totally closes the door on a subset of bug classes or bugs. Even for the stock ASAN case, it appears that it does (linear buffer overflows for a start).

There is certainly more room for exploration in this space.

Thursday, June 5, 2014

Execute without read

A couple of years ago, during an idle moment, I wondered what we could do if we had the hardware CPU primitive of pages with permissions execute-only (i.e. no read and write): https://twitter.com/scarybeasts/status/174901935340666881

It turns out that aarch64 has exactly such support. Here's support heading in to the Linux kernel:

https://git.kernel.org/cgit/linux/kernel/git/cmarinas/linux-aarch64.git/commit/?h=upstream&id=bc07c2c6e9ed125d362af0214b6313dca180cb08

The original idea was to defeat ROP by having all of the instructions randomized a bit on a per-install basis. You know, the usual tricks such as applying equivalence transforms on the opcode stream. Such an approach would have some obvious downsides such as diagnosability and let's face it, implementing this would also feel a bit hacky. Can we do better?

Maybe we can. The original idea focused on the attacker knowing where the binaries are in virtual address space, but not knowing or being able to read or otherwise predict the content. What if we instead keep the binary content stable but try and make sure the attacker cannot discern the location of the binaries? With enough ASLR entropy, this would be an interesting approach.

For the sake of the exercise, imagine the attacker has the most powerful of bugs: an arbitrary read/write primitive relative to an existing heap location. The attacker can follow heap pointers to the stack, the BSS, vtables, etc. At first, this sounds prohibitively hard to deal with. But for every way the attacker might try to leak the address of the binary, there currently seems to be a solution:

  • The heap is riddled with vtable pointers. If the attacker follows a vtable pointer, they get to read function pointers and the location of the binary is revealed. We fix this in one of two ways: either get sneaky and turn vtables into code (jmp 0xblah) instead of data, and reuse our exec-without-read primitive. Or we burn a register (aarch64 has lots) as a storage for a secret ASLR base for the binary.
  • The heap is riddled with raw function pointers. We can redo function pointers as something like single-slot vtables and use the above trick. We don't want to directly store function pointers in writable memory as a relative position to our secret register, because the attacker could then easily jump to an arbitrary point in the binary.
  • The BSS and data sections are typically stacked adjacent to the binary. We need to not do this, so that pointers into the BSS and data sections do not reveal the location of the binary.
  • The stack contains saved return addresses. These return addresses reveal the address of the binary. And for sure, the heap will contain pointers to the stack from time to time. Separating your stack into control flow and data will sort this out -- perhaps burning another register to keep the control flow stack separate and at a secret location.
  • JIT engines are a pain. And your heap is going to contain chains of pointers leading to the JIT pages. Depending on the type of JIT engine, there are various tricks that can be pulled. Enumerating them here is going to make the post too long. Some of the more amusing tricks including having the kernel ban syscalls from a writable page.
Perhaps at this point we decide that the hacks are piling up and add an indirection to all indirect jumps that uses a secret register for the binary location, and an offset into a table of valid jump locations. (I think this maybe where @comex was heading in a tweet in a discussion today: https://twitter.com/comex/status/474656633281196032)

Such a system isn't going to be invulnerable to memory corruption, but it _is_ going to be a significant pain to attack. The most obvious remaining attack is probably to read a couple of different vtable pointers and interchange them, calling an arbitrary attacker-chosen _existing function_ in the binary. If your binary has function pointers to system() in the heap, you're going to be in trouble. But generally, going after the kernel is going to be hard. Valid functions in your binary are unlikely to have the side effect of calling syscalls with bad parameters.

We also find ourselves wondering if we've sort-of re-implemented something like NaCl, although the performance characteristics and granularity of attacker-chosen code blocks will be different.

Crazy idea? Plausible direction?

[Thanks to Lee Campbell for helping with discussions and this blog post]

Friday, March 21, 2014

Together, we can make a difference

A couple of weeks back, I released a popular spreadsheet which lists many of the Adobe Flash Player 0-days used to harm people in the wild since 2010. I counted 18 and countless kind Twitterers pointed out some I may have missed. It was an interesting exercise, of course with an ulterior motive!

Looking beyond the raw counts, the spreadsheet shouts two items:
  • We should want to make a difference. The harm done from all these 0-days is just a litany of awfulness. We have harm to democracy activists and the human rights organizations that try to help these people. We have harm to American defense interests, aka. espionage. We have harm to corporations, aka. theft and economic damage.
  • We can make a difference! If you look at the data, you'll see 7 memory corruption 0-days in a year, starting mid-2010. After this year, Tavis Ormandy's famous Flash security rampage landed (80+ fixes), with follow-up patches such as 7 fixes here. Almost a year passes between Flash memory corruption 0-days after Tavis' work. You should call him a hero. (You should also call Mateusz Jurczyk, Gynvael Coldwind and Fermin Serna heroes too. They continued Tavis' work, have a look at the CVE count in this Adobe advisory to appreciate their work.)
Whilst it's true that Flash 0-days have seen a resurgence in Dec 2013 - Feb 2014, this does not invalidate the data that the whitehat community made a difference in 2010 - 2011 onwards. If anything, the data suggests that attackers have regrouped and refocused their research efforts to target areas that are still fertile. We can certainly do the same and put down this resurgence.

How you can help make a difference

Join us in the whitehat world. When you entered the greyhat world, they told you you'd be helping catch terrorists, didn't they? Recent and ongoing revelations show that no, in fact the biggest use of your work was enabling mass surveillance, the compromise of foreign nations and even the compromise of foreign corporations. If you want to make an actual difference, see above for where defensive help is needed.

Join us working on Flash and other important software. Many of us are working hard to provide reasonable avenues of reward for those who work on important software in the whitehat community. For example, the Internet Bug Bounty includes Flash as a category. For Flash vulnerabilities where exploitability is near-certain, we're rewarding up to $10,000 -- we have rewarded at this level three times already. We also anticipate $5,000 as a popular reward level for vulnerabilities that are likely exploitable but not proven. I previously blogged about $10,000 example here.

What are you waiting for? Join us and we'll make a difference. You'll get some good coin as a side-effect.

Thursday, February 20, 2014

Internet Bug Bounty issues its first $10,000 reward

One of my side projects is as an adviser and panelist for the non-profit Internet Bug Bounty (IBB). We recently added Adobe Flash Player as in scope for rewards.

Earlier today, David Rude collected $10,000 for a vulnerability recently fixed in APSB13-28. My thoughts on this are too long to fit into a tweet, so I summarize them here:

  • This shows that the IBB is serious about rewarding research which makes us all safer. $10,000 is a respectable reward by modern bug bounty program standards. It is also shows that when we give the reward range as "$2000 - $5000+", we are serious about that little plus character!
  • David Rude is a hero. This vulnerability was found being exploited in the wild. Recent research by Citizen Lab has linked the exploit to a morally dubious company, targeting of journalists and regimes with poor human rights records. Getting this bug fixed is a service to all internet users, democracy and human rights.
  • The IBB culture is to err on the side of paying. Note that David did not discover the vulnerability himself; he discovered someone else using it. IBB culture is to look mainly at whether a given discovery or piece of research helped make us all safer. Our aim is to motivate and incentivize any high-impact work that leads to a safer internet for all.
  • The vulnerability was never in fact reported to IBB! Wait, wut? It's true. The vulnerability went via Adobe's standard channels. IBB does not want or need details of unfixed vulnerabilities -- that would violate strict need-to-know handling. Once a public advisory and fix is issued, researchers or their friends may file IBB bugs to nominate their bugs for reward. Or, for important categories such as Flash or Windows / Linux kernel bugs, panel members keep an eye out for high impact disclosures and nominate on the researchers' behalf. Because we care.
Join us for the common good of a safer internet. You can help by doing your research in the open, targeting high-impact vulnerabilities or even becoming a new corporate sponsor. If we all pull together we can make a difference.

Sunday, December 29, 2013

vtable protections: fast and thorough?

Recently, there's been a reasonable amount of activity in the vtable protection space. Most of it is compiler-based. For example, there's the GCC-based virtual table verification, aka. VTV. There are also multiple experiments based on clang / LLVM and of course MSVC's vtguard. In the non-compiler space, there's Blink's heap partitioning, enabled by PartitionAlloc.

It seems, though, that these various techniques require the user to choose between "fast" or "thorough protection". This isn't ideal. Shortly, I'll document my own idea for how to try and get both fast and thorough. But first, a recap on what we mean by fast and thorough.

Fast vtable protection

Protecting vtables typically involves inserting machine instructions around vtable pointer load or virtual calls. Going fast is simple: only insert a very small number of fast instructions (i.e. no hard-to-predict branches). This is the approach taken by vtguard. If you look at page 14 in the vtguard PDF linked above, you'll see that there's just a single cmp and a single jne (short, and never taken in normal execution) added to the hot path.

Tangentially, another task commonly undertaken when adding vtable protections to a given program is to remove as many virtual calls as possible, by annotating classes and methods with the "final" keyword and/or applying whole-program optimizations.

Thorough vtable protection

Describing what we want in a thorough vtable protection is a little more involved. We want:

  • Defeating ASLR does not defeat the vtable check. (vtguard lacks this property, whereas the GCC implementation has it.)
  • Only a valid vtable pointer can be used.
  • Furthermore, only a vtable pointer corresponding to the correct hierarchy for the call site can be used. 
  • Ideally, only a vtable pointer corresponding to the correct hierarchy level for the call site can be used.

A fast solution for thorough vtable protection?

How can we get all of the protections above and get them fast? My idea revolves around separating the problem into two pieces:

  1. Work out whether we can trust the vtable pointer or not.
  2. Validate that the class type represented by the vtable pointer is appropriate for the call site.
To trust or not to trust?

Current schemes trust the vtable pointer or not, based either on an some secret (vtguard, xor-based LLVM approach), a fixed table of valid values (GCC, some LLVM approaches) or by constraining values that might appear in the vtable position (heap partitioning).

The new scheme would be to reserve a certain portion of the address space for vtables. We know that nothing else can be mapped there, so by suitably masking any proposed vtable pointer, we know it is valid. I haven't fully thought this through for 32-bit, but look at this 64-bit variant:
  • Host vtables in the lower 4GB of address space.
  • Use the dereference of a 32-bit register to load the vtable entry. This provides masking for free and even saves a byte in the instruction sequence. It works because loading 4-bytes into a 64-bit register zero extends the result.
  • Optionally, save memory by having the compiler use 4-byte vtables.

This scheme is approximately free, maybe even performance positive in some situations. Furthermore, one possible implementation is to stop somewhere around here for a very fast protection scheme that is "ok" in thoroughness.

On the downside, you've lost the 64-bit invariant that "nothing is mapped in the bottom 4GB", but the percentage of space used is going to be small. If that bothers us, then we can use the same trick to load a 4-byte vtable pointer and then "or" it with 0x100000000 (use bts if you dare) or some other value.

Validating class type

Once you know you trust your vtable pointer, validating the class type becomes a lot simpler. Instead of messing with secrets inside the vtable, you can just store a compact representation of the class type inside the vtable, with the aim of satisfying validation needs with a single compare.

The one trick we want to play is to make it easy to validate various different positions in a class hierarchy with minimal work. To do this, we can store class details in a hierarchical format. To take a simple case, imagine that we have the following classes in the system:

A1, A1:B1, A2, A2:B1, A2:B1:C1

We encode these using one byte per hierarchy level, the basemost class being the LSB: 00000001, 00000101, 00000002, 00000102, 00010102. (Note that this will be an approximation. For example, if you have more than 256 basemost classes with virtual functions, you would need to represent the first level with 2 or more bytes.)

Finally, our "is this object of the correct type for the callsite?" check becomes a simple compare. Depending on the position in the hierarchy, we may be able to achieve the compare with no masking and therefore a single instruction.

For example, for a call site expecting an object of type A1, it's just "cmpb $1, (%eax)". That's a 4-byte sequence, which is much shorter than the 10-byte sequence noted in the vtguard PDF. For a call site expecting an object of type A2:B1, it's "cmpw $0x102, (%eax)".

Closing notes

Will it work well? Who knows. I haven't had time to implement this, nor am I likely to in the near future. Feel free to take this and run with it.

Note that this idea doesn't cover what to do with raw function pointer calls. If you want to head towards complete control flow integrity, you'll want to look at protecting those, as well as return addresses (the current canary-based stack defenses do nothing against an arbitrary read/write primitive).

Sunday, February 3, 2013

Exploiting 64-bit Linux like a boss

Back in November 2012, a Chrome Releases blog post mysteriously stated: "Congratulations to Pinkie Pie for completing challenge: 64-bit exploit".

Chrome patches and autoupdates bugs pretty fast but this is a WebKit bug and not every consumer of WebKit patches bugs particularly quickly. So I've waited a few months to release a full breakdown of the exploit. The exploit is notable because it is against 64-bit Linux. 64-bit exploits are generally harder than 32-bit exploits for various reasons, including the fact that some types of heap sprays are off the table. On top of that, Linux ASLR is generally better than Windows ASLR (although not perfect). For example, Pinkie Pie's Pwnium 2 exploit defeated Win 7 ASLR by relying on a statically-addressed system object! That sort of nonsense is generally absent from Linux ASLR.

Without any further ado, I'll paste my raw notes from the exploit deconstruction below. The number of different techniques used and steps involved is quite impressive.

The bug
A single WebKit use-after-free bug was used to gain code execution. The logic flaw in WebKit was reasonably simple: when a WebCore::HTMLVideoElement is garbage collected, the base class member WebCore::HTMLMediaElement::m_player -- a WebCore::MediaPlayer -- is freed. A different object, a WebCore::MediaSource, holds a stale pointer to the freed WebCore::MediaPlayer. The stale pointer can be prodded indirectly via Javascript methods on either the JS MediaSource object, or JS SourceBuffer objects owned by the JS MediaSource.

The exploit
The exploit is moderately complicated, with multiple steps and techniques used. Pinkie Pie states that the complexity is warranted and generally caused by limited lack of control, and therefore limited options for making progress at each stage.

The exploit steps are as follows:

1. Allocate a large number of RTCIceCandidate objects (100000) and then unreference a small subset of them.
   tempia = new Uint32Array(176/4);
   rtcs = [];
   rtcstring = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
   rtcdesc = {'candidate': rtcstring, 'sdpMid': rtcstring}
   for(var i = 0; i < 100000; i++) {
       rtcs.push(new RTCIceCandidate(rtcdesc));
   }
   for(var i = 0; i < 10000; i++) rtcs[i] = null;
   for(var i = 90000; i < 100000; i++) rtcs[i] = null;

This step indirectly creates a lot of WebCore::WebCoreStringResource (v8 specific) objects and a later garbage collection will free some subset of them.
These objects are 24 bytes in size (fitting into a tcmalloc slab of 32 byte sized allocations), so it means that any future 24 byte allocation has a large probability of being placed directly before a WebCore::WebCoreStringResource object. This is significant later.
A 176-byte sized buffer is also allocated.

2. Trigger free of MediaPlayer and the 176-byte sized buffer; allocate another MediaSource object.
   buffer = ms.addSourceBuffer('video/webm; codecs="vorbis,vp8"');
   vid.parentNode.removeChild(vid);
   vid = null;
   gc();
   tempia = null;
   gc();
   ms2 = new WebKitMediaSource();

   sbl = ms2.sourceBuffers;
The WebCore::MediaPlayer is 264 bytes in size (tcmalloc bucket 257 - 288). When it is freed, many child objects are also freed. The only important one is a 168 byte sized WebKit::WebMediaPlayerClientImpl object (tcmalloc bucket 161 - 176).
Allocation of the WebCore::MediaSource (176 bytes) also subsequently allocates a WebCore::SourceBufferList child object (168 bytes). The free of the temporary 176 byte buffer (tempia) is to ensure that its freed slot is used for the WebCore::MediaSource object, leaving the freed slot that was occupied by a WebKit::WebMediaPlayerClientImpl to be occupied by a new WebCore::SourceBufferList object.

3. Call vtable of freed WebMediaPlayerClientImpl.
   buffer.timestampOffset = 42; // free
In C++, this triggers the call chain WebCore::SourceBuffer -> WebCore::MediaSource -> WebCore::MediaPlayer -> (virtual) WebKit::WebMediaPlayerClientImpl.
You’ll notice that the call chain bounces through the WebCore::MediaPlayer, which is freed. However, the only access is to the WebCore::MediaPlayer::m_private member at offset 72. delete’ing the object only interferes with the first 16 bytes (on account of tcmalloc writing two freelist pointers) and the WebCore::MediaPlayer::m_mediaPlayerClient member. The WebCore::MediaPlayer free slot isn’t otherwise meaningfully re-used by this point.

What happens next is fascinating. WebCore::MediaPlayer::sourceSetTimestampOffset dissassembles to:
   0x00007f61a0ced4c0 <+0>: mov    rdi,QWORD PTR [rdi+0x48]
   0x00007f61a0ced4c4 <+4>: mov    rax,QWORD PTR [rdi]
   0x00007f61a0ced4c7 <+7>: mov    rax,QWORD PTR [rax+0x208]
   0x00007f61a0ced4ce <+14>: jmp    rax

This loads the vtable for the WebCore::MediaPlayer::m_private member and calls the vtable function at 0x208. WebCore::MediaPlayer::m_private is supposed to be a WebKit::WebMediaPlayerClientImpl object but a WebCore::SourceBufferList was overlayed there. WebCore::SourceBufferList objects have a vtable, but a much smaller one! Offset 0x208 in this vtable hits a vtable function in a totally different vtable, specifically WebCore::RefCountedSupplement::~RefCountedSupplement, which disassembles to:
   0x00007ffd9ec51e00 <+0>: lea    rax,[rip+0x3276969]
   0x00007ffd9ec51e07 <+7>: mov    QWORD PTR [rdi],rax
   0x00007ffd9ec51e0a <+10>: jmp    0x7ffd9e5b2c80 <
WTF::fastFree(void*)>

As these opcodes execute, rdi is a this pointer for a WebCore::SourceBufferList object (which the calling code believed was a this pointer to a WebKit::WebMediaPlayerClientImpl object). As you can see, the side effects of these opcodes are:
- Trash the vtable pointer of the WebCore::SourceBufferList object.
- Do a free(this), i.e free the WebCore::SourceBufferList object.
- Return cleanly to the caller.

4. Use HTML5 WebDatabase functionality to allocate a SQLStatement as a side effect.
   transaction.executeSql('derp', [], function() {}, function() {});
   slength = sbl.length;
A WebCore::SQLStatement object is 176 bytes in size. So it is allocated into the slot just vacated by free’ing the WebCore::SourceBufferList object in step 3 above. This is the same slot that we free’d the WebKit::WebMediaPlayerClientImpl from.
There are now two Javascript objects pointing to freed objects: a direct handle to a freed WebCore::SourceBufferList (sbl) and an indirect handle to a freed WebKit::WebMediaPlayerClientImpl (buffer).
At this time, a call is made in Javascript to sbl.length. It is not required for the exploit and nothing is done with the integer result, but looking at this call under the covers is instructive.
To return the length, a 64-bit size_t is read from offset 136 into the WebCore::SourceBufferList object. Since a WebCore::SQLStatement was put on top of the freed WebCore::SourceBufferList, the actual value read is a WebCore::SQLStatement::m_statementErrorCallbackWrapper::m_callback member pointer. Leaking this value to Javascript might be useful as it is a heap address. However, Javascript lengths are 32-bit so only the lower 32-bits of the address are leaked. The entropy that’s important for ASLR on 64-bit Linux is largely in the next 8 bits above the bottom 32 bits, so the heap address cannot be usefully leaked!
Exploitation of similar overlap situations would not be a problem on systems with 32-bit pointers.

5. Abuse overlapping fields in SourceBufferList vs. SQLStatement.
   sb = sbl[0xa8/8];
Next, the Javascript array index operator is used. At this time, the Javascript handle to the WebCore::SourceBufferList is actually backed by a WebCore::SQLStatement object at the C++ level. The WebCore::SourceBufferList::m_list member is a WTF::Vector and that starts with two important 64-bit fields: a length and a pointer to the underlying buffer.
As covered above, the length now maps to a pointer value. A pointer value, when treated as an integer, will be very large, effectively sizing the vector massively. And the vector’s underlying buffer pointer now maps to the member SQLStatement::m_statementErrorCallbackWrapper::m_scriptExecutionContext.

Therefore, the Javascript array operator on JS SourceBufferList will return a JS SourceBuffer object which is backed in C++ by a pointer pulled from somewhere in a C++ WebCore::ScriptExecutionContent object, depending on the array index.

The exploit uses array index 21, which corresponds to offset 168, or WebCore::ScriptExecutionContext::m_pendingExceptions. This is a pointer to a WTF::Vector. So, there is now a Javascript handle to a JS SourceBuffer object which is really backed by a WTF::Vector.

6. Read vtable value as a Javascript number.
   converterF64[0] = sb.timestampOffset;
In C++, the timestampOffset property is read from a 64-bit double at offset 32 of the WebCore::SourceBuffer object. The WebCore::SourceBuffer object is currently backed by a WTF::Vector object, which is 24 bytes in size and lives in a 32 byte tcmalloc slot. Therefore, a read at offset 32 will in fact read from the beginning of the next tcmalloc slot. Looking back to step 1, it was arranged to be likely that the adjacent 32 byte slot will contain a WebCore::WebCoreStringResource object. Therefore, the WebCore::WebCoreStringResource vtable is read and returned to Javascript as a number. Javascript numbers are 64-bit doubles so there are no truncation issues like those discussed with reading an integer length above in step 4.

That’s a lot of effort, but finally the exploit has leaked a vtable value to Javascript. For a given build of Chrome, it is now easy to calculate the exact address of all opcodes, functions, etc. in the binary.

7. Re-trigger use-after-free and back freed object with array buffer.
   buffer2 = ms3.addSourceBuffer('video/webm; codecs="vorbis,vp8"');
   vid2.parentNode.removeChild(vid2);
   vid2 = null;
   gc();
   var ia = new Uint32Array(168/4);
   rtc2 = new webkitRTCPeerConnection({'iceServers': []});
This time, the freed WebKit::WebMediaPlayerClientImpl is replaced with a 168 raw byte buffer that can be read and written through Javascript. This is now a useful primitive because ASLR was defeated and a useful vtable pointer value can be put in the first 8 bytes of the raw byte buffer.
A WebCore::RTCPeerConnection is also allocated (264 bytes) to occupy the slot for the freed WebCore::MediaPlayer. This protects the freed WebCore::MediaPlayer from corruption. Significantly, it makes sure nothing overwrites the WebCore::MediaPlayer::m_private pointer. This pointer is needed intact. It is at offset 72 and WebCore::RTCPeerConnection does not overwrite that field during construction.

8. Leak address of a heap buffer under Javascript control.
   add64(converterI32, 0, converterI32, 0, -prepdata['found_vt']);
   add64(ia, 0, converterI32, 0, prepdata['mov_rdx_112_rdi_pp']);
   add64(ia, 0, ia, 0, -0x1e8);
   var ib8 = new Uint8Array(0x10000);
   var ib = new Uint32Array(ib8.buffer);
   buffer2.append(ib8);
   var ibAddr = [ia[112/4], ia[112/4 + 1]];
Using knowledge of the binary layout, a vtable value is chosen that will result in the WebCore::MediaPlayer::sourceAppend vtable call site calling the function v8::internal::HStoreNamedField::SetSideEffectDominator. An appropriate function name. It disassembles to:
   0x00007f153efd7340 <+0>: mov    QWORD PTR [rdi+0x70],rdx
   0x00007f153efd7344 <+4>: ret    
As can be seen, the value of rdx (the 2nd non-this function parameter) is written to offset 112 of this. this is backed by a raw buffer pointer for the ia Javascript Uint32Array and rdx in the context of WebCore::MediaPlayer::sourceAppend is a raw buffer pointer for the ib Javscript Uint32Array.
Therefore, the address of a heap buffer under the control of Javascript has been leaked to Javascript.

9. Proceed as normal.
The exploit now has control over a vtable pointer. It can point the vtable pointer at a heap buffer where the contents can be controlled arbitrarily. The exploit is free to start ROP chains etc.
As it happens, the exploit payload is expressed in terms of valid full function calls. This is achieved by bouncing into a useful sequence of opcodes in a template base::internal::Invoker<3>:
   0x00007f153fc71d40 <+0>: mov    rax,rdi
   0x00007f153fc71d43 <+3>: lea    rcx,[rdi+0x30]
   0x00007f153fc71d47 <+7>: mov    rsi,QWORD PTR [rdi+0x20]
   0x00007f153fc71d4b <+11>: mov    rdx,QWORD PTR [rdi+0x28]
   0x00007f153fc71d4f <+15>: mov    rax,QWORD PTR [rax+0x10]
   0x00007f153fc71d53 <+19>: mov    rdi,QWORD PTR [rdi+0x18]
   0x00007f153fc71d57 <+23>: jmp    rax
As can be seen, these opcodes pull a jump target, a new this pointer and two function arguments from the current this pointer. A very useful construct.