Analysis of a Chrome Zero Day: CVE-2019-5786

Google Chrome CVE-2019-5786 Vulnerability Exploit

Google Chrome CVE-2019-5786 Vulnerability Exploit

1. Introduction

On March 1st, Google published an advisory [1] for a use-after-free in the Chrome implementation of the FileReader API (CVE 2019-5786). Clement Lecigne from Google Threat Analysis Group reported the bug as being exploited in the wild and targeting Windows 7, 32-bit platforms. The exploit leads to code execution in the Renderer process, and a second exploit was used to fully compromise the host system [2]. This blog is a technical write-up detailing the first bug and how to find more information about it. At the time of writing, the bug report [2b] is still sealed. Default installation of Chrome will install updates automatically, and users running the latest version of Chrome are already protected against that bug. To make sure you’re running the patched version, visit chrome://version, the version number displayed on the page should be 72.0.3626.121 or greater.

2. Information gathering

2.1 The bug fix

Most of the Chrome codebase is based on the Chromium open source project. The bug we are looking at is contained inside the open source code, so we can directly look at what was fixed in the new release pertaining to the FileReader API. Conveniently, Google shares the changelog for its new release [3].

We can see that there’s only one commit that modifies files related to the FileReader API, with the following message:

The message hints that having multiple references to the same underlying ArrayBuffer is a bad thing. It is not clear what it means right now, but the following paragraphs will work on figuring out what wisdom lies hidden in this message.

For starters, we can look at the commit diff [3b] and see what changed. For ease of reading, here is a comparison of the function before and after the patch.

The old one:

The new one:

The two versions can be found on GitHub at [4a] and [4b]. This change modifies the behavior of the ArrayBufferResult function that is responsible for returning data when a user wants to access the FileReader.result member.
The behavior of the function is as follows: if the result is already ‘cached,’ return that. If not, there are two cases; if the data has finished loading, create a DOMArrayBuffer, cache the result, and returns it. If not, it creates a temporary DOMArrayBuffer and returns that instead. The difference between the unpatched and patched version is how that temporary DOMArrayBuffer is handled, in case of a partial load. In one case, we can see a call to:

This prompted us to go down a few more rabbit holes. Let us compare what is going on in both the unpatched and patched situation.

We can start with the patched version, as it is the simplest to understand. We can see a call to ArrayBuffer::Create that takes two arguments, a pointer to the data and its length (the function is defined in the source tree at /third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer.h)

This basically creates a new ArrayBuffer, wraps it into a scoped_refptr<ArrayBuffer> and then copies the data into it. The scoped_refptr is a way for Chromium to handle reference counting [5]. For readers unfamiliar with the notion, the idea is to keep track of how many times an object is being referenced. When creating a new instance of a scoped_refptr, the reference count for the underlying object is incremented; when the object exits its scope, the reference count is decremented. When that reference count reaches 0, the object is deleted (and for the curious, Chrome will kill a process if the reference count overflows….). As we’re looking for a potential use-after-free, knowing that the buffer is ref-counted closes some avenues of exploitation.

In the unpatched version, instead of calling ArrayBuffer::Create, the code uses the return value of ArrayBufferBuilder::ToArrayBuffer() (from third_party/blink/renderer/platform/wtf/typed_arrays/

Here is yet another rabbit hole to dive into (but we will keep it high level).  Depending on the value of bytes_used_), the function will either return its buffer, or a Sliced version of it (i.e. a new ArrayBuffer of a smaller size, that contains a copy of the data)

To sum up what we have so far, in all the code paths we have looked at, they all return a copy of the data instead of the actual buffer, unless we run the unpatched code, and the buffer we try to access is `fully used` (per the comment in ArrayBufferBuilder::ToArrayBuffer()).
Because of the implementation of the FileReaderLoader object, the buffer_->ByteLength() is the pre-allocated size of the buffer, which correspond to the size of the data we want to load (this will be relevant later on).
If we now remember the commit message and what the bad scenario was, it looks like the only situation to exploit the bug is to access multiple times the ArrayBufferBuilder::ToArrayBuffer(), before the finished_loading is set to true, but after the data is fully loaded.

To wrap up this part of the code review, let us look at the behavior of the DOMArrayBuffer::Create function that is being called in both patched/unpatched cases, the case interesting to us is when we have the following call DOMArrayBuffer::Create(raw_data_->ToArrayBuffer());

From third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h:

Something interesting to look at is the use of std::move, which has the semantic of transferring ownership.
For instance, in the following snippet:

then `b` takes ownership of what belonged to `a` (`b` now contains “hello”) and `a` is now in a somewhat undefined state (C++11 specs explain that in more precise terms)).

In our current situation, what is going on here is somewhat confusing [6a] [6b]. The object returned by ArrayBufferBuilder::ToArrayBuffer() is already a scoped_refptr<ArrayBuffer>. I believe the meaning of all this, is that when calling ToArrayBuffer(), the refcount on the ArrayBuffer is increased by one, and the std::move takes ownership of that instance of the refcounted object (as opposed to the one owned by the ArrayBufferBuilder). Calling ToArrayBuffer() 10 times will increase the refcount by 10, but all the return values will be valid (as opposed to the toy example with the strings `a` and `b` mentioned above where operating on `a` would result in unexpected behavior).
This closes an obvious case of use-after-free where the buffer_ object from the ArrayBufferBuilder would get corrupted if we would call ToArrayBuffer() multiple times during the sweet spot described above.

2.2 FileReader API

Another angle of approach for figuring out how to exploit this bug is to look at the API that is available to us from JavaScript and see if we can come up with a way to reach the sweet spot we were looking at.

We can get all the information we want from Mozilla web docs [7]. Our options are fairly terse; we can call readAsXXX functions on either Blob or File, we can abort the read, and finally there are a couple of events to which we can register callbacks (onloadstart, onprogress, onloadend, …).

The onprogress events sounds like the most interesting one, as it is being called while data is loading, but before the loading is finished. If we look at the source file, we can see that the logic behind the invocation of this event is to fire every 50ms (or so) when data is received. Let us have a look at how this behaves in a real system…

3. Testing in a web-browser

3.1 Getting started

The first thing we want to do is download a vulnerable version of the code. There are some pretty useful resources out there [8] where one can download older builds rather than having to build them yourself.

Something interesting to note is that there is also a separate zip file that has `syms` in its name. You can also download to get debug symbols for the build (in the form of .pdb files). Debuggers and disassemblers can import those symbols which will make your life way easier as every function will be renamed by its actual name in the source code.

3.2 Attaching a debugger

Chromium is a complex software and multiple processes communicate together which makes debugging harder. The most efficient way to debug it is to start Chromium normally and then attach the debugger to the process you want to exploit. The code we are debugging is running in the renderer process, and the functions we were looking at are exposed by chrome_child.dll (those details were found by trial and error, attaching to any Chrome process, and looking for function names of interest).

If you want to import symbols in x64dbg, a possible solution is to go in the Symbol pane, right click on the .dll/.exe you want to import the symbols for and select Download symbols. It may fail if the symbol server setting is not configured properly, but it will still create the directory structure in x64dbg’s `symbols` directory, where you can put the .pdb files you’ve previously downloaded.

3.3 Looking for the exploitable code path

Not that we have downloaded an unpatched version of Chromium, and we know how to attach a debugger, let us write some JavaScript to see if we can hit the code path we care about.

To sum up what is going on here, we create a Blob that we pass to the FileReader. We register a callback to the progress event and, when the event is invoked, we try to access multiple times the result from the reader. We have seen previously that the data needs to be fully loaded (that is why we check the size of the buffer) and if we get multiple DOMArrayBuffer with the same backing ArrayBuffer, they should appear to be to separate objects to JavaScript (hence the equality test). Finally, to double check we have indeed two different objects backed by the same buffer, we create views to modify the underlying data and we verify that modify one modifies the other as well.

There is an unfortunate issue that we had not foreseen: the progress event is not called frequently, so we have to load a really large array in order to force the process to take some time and trigger the event multiple times. There might be better ways of doing so (maybe the Google bug report will reveal one!) but all the attempts to create a slow loading object were a failure (using a Proxy, extending the Blob class…). The loading is tied to a Mojo Pipe, so exposing MojoJS could be a way of having more control as well but it seems unrealistic in an attacker scenario as this is the entry point of the attack. See [9] for an example for that approach.

3.4 Causing a crash

So, now that we have figured out how to get into the code path that is vulnerable, how do we exploit it? This was definitely the hardest question to answer, and this paragraph is meant to share the process to find an answer to that question.

We have seen that the underlying ArrayBuffer is refcounted, so it is unlikely we’ll be able to magically free it by just getting garbage collected from some of the DOMArrayBuffer we’ve obtained. Overflowing the refcount sounds like a fun idea, but if we try by hand to modify the refcount value to be near its maximum value (via x64dbg) and see what happens… well, the process crashes. Finally, we cannot do much on those ArrayBuffers; we can change their content but not their size, nor can we manually free them…
Not being familiar enough with the codebase, the best approach then is to pour through various bug reports that mention use-after-free, ArrayBuffer, etc., and see what people did or talked about. There must be some assumption somewhere that a DOMArrayBuffer owns its underlying memory, and that is an assumption we know we are breaking.
After some searching, we started to find some interesting comments like this one [10a] and this one [10b]. Those two links talk about various situation where DOMArrayBuffer gets externalized, transferred and neutered. We are not familiar with those terms, but from the context it sounds like when this happens, the ownership of the memory is transferred to somebody else. That sounds pretty perfect for us as we want the underlying buffer to be freed (as we are hunting for a use-after-free).
The use-after-free in WebAudio shows us how to get our ArrayBuffer “transferred” so let’s try that!

And as seen in the debugger:

The memory being dereferenced is in ECX (we also have EAX == 0 but that’s because we’re looking at the first item in the view). The address looks valid, but it isn’t. ECX contains the address where the raw data of our buffer was stored (the AAAAA…) but because it got freed, the system unmapped the pages that held it, causing the access violation (we’re trying to access an unmapped memory address). We reached the use-after-free we were looking for!

4. Exploit considerations and next steps

4.1 Exploit

It is not the point of this document to illustrate how to push beyond the use-after-free to get full code execution (in fact Exodus have released a blog and a working exploit roughly coinciding with the timing of this publication). However, there are some interesting comments to be made.
Due to the way we are triggering the use-after-free, we are ending up with a very large buffer unallocated. The usual way to exploit a use-after-free is to get a new object allocated on top of the freed region to create some sort of confusion. Here, we are freeing the raw memory that is used to back the data of our ArrayBuffer. That is great because we can read/write over a large region. Yet, a problem in this approach is that because the memory region is really large, there is no one object that would just fit in. If we had a small buffer, we could create lots of objects that have that specific size and hope one would be allocated there. Here it is harder because we need to wait that until that memory is reclaimed by the heap for unrelated objects. On Windows 10 64-bit, it is hard because of how random allocations are, and the entropy available for random addresses. On Windows 7 32-bit, it is much easier as the address space is much smaller, and the heap allocation is more deterministic. Allocating a 10k object might be enough to have some metadata land within the address space we can control.
The second interesting aspect is that because we are going to dereference a region that has been unmapped, if the 10k allocation mentioned above fails to allocate at least one object in that area we control, then we are out of luck; we will get an access violation and the process will die. There are ways to make this step more reliable, such as the iframe method described here [11]
An example on how to move on if one can corrupt the metadata of a JavaScript object can be found here [12].

4.2 Next step

Once an attacker has gained code execution inside the renderer process they are still limited by the sandbox. In the exploit found in the wild, the attacker used a second 0-day that targeted the Windows Kernel to escape the sandbox. A write up describing that exploit was recently released by the 360CoreSec here [13].

5. Conclusion

By looking at the commit that fixed the bug and hunting down hints and similar fixes we were able to recover the likely path towards exploitation. Once again, we can see that modern mitigations introduced in the later version of Windows makes life way harder on attackers and we should celebrate those wins from the defensive side. Also, Google is extremely efficient and aggressive in its patching strategy, and most of its user base will have already seamlessly updated to the latest version of Chrome.



29 thoughts on “Analysis of a Chrome Zero Day: CVE-2019-5786

  1. Are an individual tired of llooking inside the hand mirror and experiencing those
    yucky red holes and bumps all over your face? Just how would yoou
    like clear, beautiful skin? We include put together every
    onee of the very best tips to hepp an individual get obvious skin the fact that you can be pleased with, aand help you in order
    to no lobger suffer from annoying blemishes.Montreal canada

    Many who suffter with acne blemishes pop this oil-filled pores.

    When you doo come to a decision to pop a zit, do that with clean hands.
    Do not forget your own fingernails, either; you want to minimize the risk of pproducing more bacteria tto the contaminated pore.
    You will more than likely see a reduction associated with zits if you can be equipped to appear your
    own zits responsibly.

    Remain hydrated to help with zits breakouts.
    Try to drink at least eight glasses associated with
    water a working day to be able to maintain clear
    skin. Driking water flushes out the poisons in your body,
    which in turn also include the skin. Nott really only wjll your body get rid acne-causing toxins, nonetheless it will probably be equipped
    to maintain the correct level of wetness, to give it a good much healthier
    glow.civil war

    If you needmontreal canada tto reduce the large, red
    pimple, try using a cold compress or maybe perhas ann cerchi inn legaice cube
    draped in a fabric. Applying tthe cold decrease just before cargo area could cause reduced redness in the morning,
    becahse tthe cold will lower the blood flow to be able to the blemish as well as puffiness
    will decrease.

    One of the best techniques to clear up acne pimples is to have a 10 minute walk outdoors just aboujt every
    day. The sunshine and new air have a highly positive effect on the skin. Make sure that anyone don’t stay outt a long time, even though,
    aas burrning the skin has a negative result as well as your acne could become even worse iin the potential.Freddy

    As you can observe, clear, wonderful skin can certainly be yours.

    You no longer own to dread looking inside the mirror just to possess all those annoying, red lumps gazing back at anyone.
    Follow the tips and even you can tell pimples to pack itss bags aand mopve
    on out there. You are now ready too be able to enjoy looking in typically the mirrr at your fresh, beautiful, clear skin.

  2. Greetings! Very useful advice within this article!

    It is the little changes that make the most important changes.
    Many thanks for sharing!| I really love your blog..
    Pleasant colors & theme. Did you develop this website yourself?
    Please reply back as I?m wanting to create my own personal blog and would love to know
    where you got this from or just what the theme is called.


  3. Its like you learn my mind!
    You appear to grasp a
    lot about this, like you wrote the guide in it or something.

    I think that you just could do with a few % to force the message house
    a bit, however other than that, that is
    blog. A great read.
    I will definitely be back.|
    I visited various websites however the audio feature for audio songs current at this
    web page is truly superb.|
    Howdy, i read your blog from time to time and i own a similar
    one and i was just curious if you get a lot of spam comments?
    If so how do you protect against it, any plugin or anything you can recommend?
    I get so much lately it?s driving me crazy so any assistance is
    very much appreciated.|
    Greetings! Very helpful advice in this particular post!

    It is the little changes that make the greatest changes.
    Many thanks for sharing!|
    I seriously love your blog.. Pleasant colors & theme.
    Did you create this web site yourself? Please reply back as I?m looking to create my own personal website and would like
    to find out where you got this from or just what the theme
    is called. Many thanks!

  4. I’m impressed, I must say. Rarely do I encounter a blog that’s both equally educative
    and amusing, and let me tell you, you have hit the nail on the
    head. Thee issue is something that not enough people are speaking intelligently about.
    Now i’m very happy that I stumbled across this during my search for something concerning this.

  5. Nice blog right here! Additionally youur web site rather a lot up fast!
    What web host are you the use of? Can I am gettfing your affiliate
    hyperlink in your host? I wish my web site loaded up as quickly as yours lol

  6. Obrigado para o postagem maravilhosa! Eu na verdade gostava de ler, você será um grande autor.
    Vou certifique-se de marcar seu blog e será voltar num futuro previsível .

    Quero encorajar que você continuar sua grande emprego ,
    tenha um bom manhã !

  7. Hi! This is my first visit to your blog! We are a group of volunteers
    and starting a new project in a community in the same niche.
    Your blog provided us useful information to work on. You have
    done a wonderful job!

  8. Unquestionably consider that which you said. Your favourite justification appeared
    to be at the net the simplest factor to take note
    of. I say to you, I definitely get irked while people think about concerns that they just do not realize about.

    You managed to hit the nail upon the highest and defined out the entire thing with no need side-effects ,
    folks can take a signal. Will probably be again to get
    more. Thank you

Leave a Reply

Your email address will not be published. Required fields are marked *