Tuesday, February 11, 2020

A day^W^W Several months in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 life of Project Zero - Part 2: The Chrome exploit of suffering

Posted by Sergei Glazunov and Mark Brand, Project Zero

Introduction

After we’d understood how cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug worked, and had passed on those details to Chrome to help cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m get started on a fix, we went back to our ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r projects. This bug remained a topic of discussion, and eventually we ran out of excuses for not trying to write an exploit for it. 

One of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 main reasons for doing this was to understand how readily exploitable bugs in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Chrome network stack are, given cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 relatively recent changes in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process architecture. Nobody in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 team had taken a serious look at exploiting an issue in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network stack in Chrome, so it would likely give some more interesting insights than an exploit targeting more well-understood areas of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 codebase, such as renderer bugs or cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 more typical browser-process sandbox escape targets.

While it may take you just a matter of minutes to read this post; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 work behind it took a little longer, with many more failures than successes, even though cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end result was a working "single-bug-chain" Chrome exploit.

Many of our failures weren’t due to any particular difficulty, and instead due to carelessness, which seems somehow more problematic when working with unreliable primitives; it’s easy to spend a whole day debugging a failing heap groom without, for example, noticing that you have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 webserver running on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 wrong port… 

Chapter 4. The Exploit.

We finished last time with quite a powerful primitive; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerability gave us a write of controlled data of a chosen size past cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of a heap allocation. There was just one major drawback to overcome — due to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 way cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug works, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocation that we’re overwriting will always be of size 0.

As most ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r allocators, tcmalloc stores it in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 smallest class size, which is "up to 16 bytes" in this case. There are two issues with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size class. Firstly, "useful" objects (i.e. cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ones that contain pointers we might want to overwrite) are usually larger than that. Secondly, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size class is racá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r congested as virtually every IPC call to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network process triggers allocations and deallocations in it. Therefore we can’t use, for example, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 "heavy" fetch API, for heap spraying / grooming. Unfortunately, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are few object types in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network process that fit into 16 bytes and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 creation of which doesn’t trigger a bunch of ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r allocations.

There was some good news too. If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network process crashes it will be silently restarted, so we would be able to use this as a crutch if we were struggling with reliability — our exploit would be able to try several times before it succeeds.

NetToMojoPendingBuffer

We were able to find an object that was suitable for constructing a "write-what-where" primitive relatively quickly by just enumerating small classes related to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network process. A new NetToMojoPendingBuffer object is created on every URLLoader::ReadMore call, so cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attacker can control cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se allocations by delaying cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 dispatch of response chunks on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 web server side.

class COMPONENT_EXPORT(NETWORK_CPP) NetToMojoPendingBuffer
    : public base::RefCountedThreadSafe {
  mojo::ScopedDataPipeProducerHandle handle_;
  void* buffer_;
};

We don’t have to worry about overwriting handle_ because when Chrome encounters an invalid handle it just returns early without crashing. The data that will get written to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buffer’s backing store is exactly cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next HTTP response chunk, so it’s also fully controlled.

There’s a problem, though — cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 primitive alone won’t suffice without a separate infoleak. An obvious idea to make it more powerful would be to perform a partial overwrite of buffer_ and subsequently corrupt an object in some ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r (hopefully more convenient) size class. However, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointer never gets assigned to a regular heap address. Instead, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 backing store for NetToMojoPendingBuffer objects is allocated inside a shared memory region that’s only used for IPC and doesn’t contain objects, so cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s nothing to corrupt cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re.

Apart from NetToMojoPendingBuffer, we couldn’t find anything in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 16-byte size class that would look immediately useful.

Going after STL containers.

Luckily, we’re not limited to C++ classes and structures. Instead, we can target arbitrarily sized buffers like container backing stores. For example, when an element is being inserted into an empty std::vector, we allocate a backing store with space for just a single element. On subsequent insertions, if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s no space left it gets doubled. Some ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r container classes operate in a similar way. Thus, if we precisely control insertions to, e.g., a vector of pointers, we can perform a partial overwrite of one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointers to turn cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug into a type confusion of some sort.

WatcherDispatcher.

A crash related to WatcherDispatcher showed up while we were experimenting with NetToMojoPendingBuffer. The WatcherDispatcher class is not specific to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network process. It’s one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 basic Mojo structures, used everywhere that IPC messages are sent and received. The class layout is as follows:

class WatcherDispatcher : public Dispatcher {
  using WatchSet = std::set<const Watch*>;
  base::Lock lock_;
  bool armed_ = false;
  bool closed_ = false;
  base::flat_map<uintptr_t, scoped_refptr> watches_;
  base::flat_map> watched_handles_;
  WatchSet ready_watches_;
  const Watch* last_watch_to_block_arming_ = nullptr;
};

class Watch : public base::RefCountedThreadSafe {
  const scoped_refptr watcher_;
  const scoped_refptr dispatcher_;
  const uintptr_t context_;
  const MojoHandleSignals signals_;
  const MojoTriggerCondition condition_;
  MojoResult last_known_result_ = MOJO_RESULT_UNKNOWN;
  MojoHandleSignalsState last_known_signals_state_ = {0, 0};
  base::Lock notification_lock_;
  bool is_cancelled_ = false;
};

MojoResult WatcherDispatcher::Close() {
  // We swap out all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 watched handle information onto cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack so we can
  // call into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir dispatchers without our own lock held.
  base::flat_map<uintptr_t, scoped_refptr> watches;
  {
    base::AutoLock lock(lock_);
    if (closed_)
      return MOJO_RESULT_INVALID_ARGUMENT;
    closed_ = true;
    std::swap(watches, watches_);
    watched_handles_.clear();
  }

  // Remove all refs from our watched dispatchers and fire cancellations.
  for (auto& entry : watches) {
    entry.second->dispatcher()->RemoveWatcherRef(this, entry.first);
    entry.second->Cancel();
  }

  return MOJO_RESULT_OK;
}

std::flat_map is actually backed by std::vector and watched_handles_ contains only one element most of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 time, which takes exactly 16 bytes. This means we can overwrite a Watch pointer!

The size of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Watch class is relatively large — 104 bytes — and because of tcmalloc we can only target objects of a similar size for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 partial overwrite. Furcá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rmore, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 target object should contain valid pointers at certain offsets to survive a call of a Watch method. Unfortunately, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network process doesn’t seem to contain a class that would meet cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 above requirements for a straightforward type-confusion.

We can take advantage of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fact that Watch is a reference-counted class though. The idea is to spray a lot of Watch-sized buffers, which tcmalloc will place next to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 actual Watch object, and hope that scoped_refptr with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 overwritten least significant byte will point to one of our buffers. The buffer should have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first 64-bit word, i.e. cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake reference counter, set to 1 and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 rest set to 0. In that case, a call to WatcherDispatcher::Close, which frees cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 scoped_refptr, will trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 deletion of our fake Watch, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 destructor will finish gracefully, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buffer will get freed.

If our buffer is scheduled to be sent to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attacker’s server or back to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer process, this will leak tcmalloc’s masked freelist pointers or, even better, some useful pointers if we managed to allocate something else cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 meantime. So, what we need now is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to create such buffers in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network process and delay sending cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m until cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 corruption has occurred.

Turns out cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network process in Chrome is also responsible for handling WebSocket connections. What’s important is that WebSocket is a low overhead protocol, and it allows transferring binary data. If we make cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 receiving end of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 connection sufficiently slow and send enough data to fill up cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 OS socket send buffer until cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 point where TCPClientSocket::Write becomes an "asynchronous" operation, subsequent calls to WebSocket::send will result in raw frame data being stored as IOBuffer objects with just two extra 32-byte allocations on each call. Moreover, we 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 buffers by modifying cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 delay on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 receiving side.

Looks like we just found a near-perfect heap spraying primitive! It has one weakness though — it’s not possible to free an individual buffer. All frames tied to a connection get freed at once eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current batch is sent or when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 connection is torn down. We obviously can’t have a WebSocket connection per spray object, and each of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 above operations induces a lot of undesired "noise" in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap. However, let’s put it aside for a moment.

The following is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 outline of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 method:



Unfortunately,
watched_handles_ quickly proved to be a suboptimal target. Some of its drawbacks are:
  • There are actually two flat_map members, but we can only use one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m since corrupting watched_handles_ will immediately trigger a crash during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RemoveWatcherRef virtual method call.
  • Each WatcherDispatcher allocation triggers a lot of "noise" in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size classes we care about.
  • There are 16 (= 256 / GCD(112, 256)) possible values for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 LSB of a pointer in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Watch size class, most of which won’t even point to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 beginning of an object.

While we were able to leak some data using this approach, its success rate was racá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r upsetting. The approach itself seemed reasonable, but we had to find a more "convenient" container to overwrite.

WebSocketFrame

It’s time to take a closer look at how sending a WebSocket frame is implemented.

class NET_EXPORT WebSocketChannel {
[...]
  std::unique_ptr data_being_sent_;
  // Data that is queued up to write after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current write completes.
  // Only non-NULL when such data actually exists.
  std::unique_ptr data_to_send_next_;
[...]
};

class WebSocketChannel::SendBuffer {
  std::vector<std::unique_ptr> frames_;
  uint64_t total_bytes_;
};

struct NET_EXPORT WebSocketFrameHeader {
  typedef int OpCode;

  bool final;
  bool reserved1;
  bool reserved2;
  bool reserved3;
  OpCode opcode;
  bool masked;
  uint64_t payload_length;
};

struct NET_EXPORT_PRIVATE WebSocketFrame {
  WebSocketFrameHeader header;
  scoped_refptr data;
};

ChannelState WebSocketChannel::SendFrameInternal(
    bool fin,
    WebSocketFrameHeader::OpCode op_code,
    scoped_refptr buffer,
    uint64_t size) {
[...]
  if (data_being_sent_) {
    // Eicá 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 link to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 WebSocket server is saturated, or several 
    // messages are being sent in a batch.
    if (!data_to_send_next_)
      data_to_send_next_ = std::make_unique();
    data_to_send_next_->AddFrame(std::move(frame));
    return CHANNEL_ALIVE;
  }

  data_being_sent_ = std::make_unique();
  data_being_sent_->AddFrame(std::move(frame));
  return WriteFrames();
}

WebSocketChannel employs two separate SendBuffer objects to store outgoing frames. When cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 connection is saturated, new frames go into data_to_send_next_. And, since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buffers are backed by an std::vector>, it can also become a target for overwriting! However, we need to figure out precisely cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 amount of data that has to be sent before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 connection becomes saturated, ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rwise data_to_send_next_’s buffer will quickly become too large to fit in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 16-byte slot. This value, which is tied to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FRAMES_ENOUGH_TO_FILL_BUFFER constant in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit, depends on both cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network and system configuration. In cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ory, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit could calculate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 value automatically; we just did it manually though for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 "localhost" and "same LAN" cases. Also, to make cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 saturation process more reliable, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SO_RCVBUF option for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 WebSocket server socket has to be changed to a relatively small value,  and data compression has to be disabled.

As mentioned above, our heap spray technique makes two extra 32-byte allocations for each "desired" allocation. Unfortunately, WebSocketFrame, a pointer to which we’re planning to overwrite, is exactly 32 bytes in size. That means unless we use some additional heap manipulation tricks, only 1/3 of all objects produced during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap spray will be of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 right type. On 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 hand, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are half as many possible values for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 LSB in this size class compared to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Watch one, with a much higher chance of pointing to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 beginning of a proper allocation. What’s even more important is, unlike WatcherDispatcher, WebSocket::Send won’t trigger any allocations in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 16-byte arena apart from resizing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 std::vector that we’re targeting, so cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap spray in that size class should be clean and tidy. On balance, this makes data_to_send_next_ a better target.

Allocation Patterns

For lack of a more robust alternative, we have to use WebSocket::Send as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 default heap manipulation tool. It’s responsible for at least:
  • Spraying with 32-byte buffers, to one of which we want to overwrite cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 WebSocketFrame pointer.
  • Inserting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 target vector entry and creating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 tied WebSocketFrame.
  • Allocating an IOBuffer object in place of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed buffer.


The objects shown in red above are "unwanted" allocations. Each of those will negatively affect cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reliability of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit, but we have no way of avoiding cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m so far and just have to hope that with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 unlimited amount of retries it won’t matter much.

Infoleak

Once we can fairly reliably overwrite cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 WebSocketFrame pointer, we’ve turned our slightly annoying primitive that only let us corrupt objects in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 16-byte bucket into a new primitive allowing us to free an allocation from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 32-byte bucket instead. Since data_to_send_next_ uses std::unique_ptr instead of scoped_refptr, we also don’t have to care about making up a fake reference counter. The only requirement for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free’d fake WebSocketFrame is that its data pointer should be null.

We can use this primitive to build quite a useful infoleak that will give us both cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 location of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Chrome binary in memory, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 location of data that we can control on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap, giving us all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 information that’s necessary to complete our exploit.

One of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 advantages of using WebSockets in our heap manipulation is that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser is going to send cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 data stored in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se frames to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server (once cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 socket is unblocked), and so if we can use this free to free cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 backing store for an IOBuffer that’s already queued to be sent, we will be able to leak cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new contents of that allocation. Additionally, since this size class matches cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocation size of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IOBuffer objects, we can replace cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free backing store with a new IOBuffer object. This gives us a leak of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IOBuffer vtable pointer, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first piece of information that we need.

However, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IOBuffer object also includes a pointer to its backing store — which is a heap allocation of a size that we control. If we ensure that this is in a size class that won’t interfere with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 rest of our heap manipulation, we can leak this pointer now, and later on in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit we can free this allocation and reuse it for something more useful.

Code Execution

Assuming that we can reuse cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 larger allocation that we leaked cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of, we could certainly be forgiven for thinking that we’re almost done here — we know where we can write some data, we know what data we should write cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re, and we have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 relatively powerful 32-byte free primitive we built for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 infoleak.

Unfortunately, as mentioned above, we don’t really have great primitives for just allocating IOBuffers or WebSocketFrames individually; good things come in pairs! While for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 infoleak we didn’t have much flexibility (we needed to free an IOBuffer backing store, and we needed to replace it with an IOBuffer object), for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next stage in our exploit we have a few options to try and increase our chances of success.

Since we’re not interested in freeing an IOBuffer backing store any more, we can move those allocations into a different size class, so that we now have only three different object types coming from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 32-byte bucket: WebSocketFrame, IOBuffer, and SendBuffer. If we can spray perfectly, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n we should be able to arrange to have 3 pairs of "target IOBuffer" and "target WebSocketFrame" for each "victim WebSocketFrame". This means that when we corrupt cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 "victim WebSocketFrame" by triggering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerability a second time, we have an equal probability of freeing eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r an IOBuffer or a WebSocketFrame.

By crafting our replacement object carefully, we can take advantage of both possibilities. We’re going to gain control of execution during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 destructor call for eicá 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 WebSocketFrame or cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IOBuffer. The only field that really matters in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 WebSocketFrame is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 data pointer, which needs to point to an IOBuffer object. Since this corresponds to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 padding bytes at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IOBuffer object, we can create a replacement object that can fill cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 space of eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r a freed IOBuffer or a freed WebSocketFrame.

When cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 replacement object is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n freed, if we replaced an IOBuffer cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n when decrementing ref_count_ results in 0, we’ll get a virtual call through our fake vtable — if we instead replaced a WebSocketFrame, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 WebSocketFrame will release its data member, which we’ve pointed to anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r fake IOBuffer, which will again result in a virtual call through our fake vtable.

Throughout all of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 above, we’ve been ignoring a minor detail — which thanks to our careful prior preparation will indeed turn out to be fairly minor — we need to get our second fake IOBuffer and our fake vtable into memory at a known address. We unfortunately can’t release cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocation that we leaked cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of earlier, since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IOBuffer object that we leaked will have been freed (as though it was cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 backing store of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IOBuffer sent back to us).

This isn’t a major issue though; we can choose for those larger allocations to go into a "quiet" bucket size. If we prepare that bucket size in advance with alternating buffers from two different websockets, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n we can release one of those websockets to ensure that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address we leak will be adjacent to a buffer from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second websocket. After we’ve leaked cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address, we can cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n release cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second websocket and replace cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 contents of this adjacent buffer with our fake objects.


In summary, we use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 backing data of a larger
IOBuffer at a known address to write controlled data into memory. This contains a fake IOBuffer vtable, togecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r with our code-reuse payload and a second fake IOBuffer. We can cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerability again, this time causing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use-after-free of eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r an IOBuffer object or a WebSocketFrame object, both of which will use pointers into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 larger IOBuffer backing data that’s (now) at a known address.

When cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 corrupted objects are freed, our payload is run and our work here is done… almost…

Recap: Component breakdown

At this point, we have quite a few moving parts, so for those of you who want to dive into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 source code for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit here’s a quick breakdown:

  • serv.py — a custom webserver that will just handle cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 request for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image file, and return cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 appropriate sequence of responses to trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerability.
  • pywebsocket.diff — a few patches to pywebsocket which remove compression and set SO_RCVBUF for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 websocket server.
  • get_chrome_offsets.py — a script that will attach to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 running browser and collect all of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 necessary offsets for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 payload. This requires frida to be installed.
  • CrossThreadSleep.py — this implements a basic sleepable wait primitive that is used to sleep individual threads in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 websocket server and wake cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m from ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r threads.
  • exploit/echo_wsh.py — a websocket handler for pywebsocket that handles several message types that will cause eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r a timed delay or a wakeable delay that allow cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365  socket-buffering manipulation that we need.
  • exploit/wake_up_wsh.py — a websocket handler for pywebsocket that handles several control messages to wake sleeping "echo" sockets.
  • exploit/exploit.html — cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 javascript code that is used to implement cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit logic.

We’ve also provided some scripts to make it easier for readers to get a working Chromium build that’s vulnerable to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue, and to get cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 rest of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 environment set up correctly:

  • get_chromium.sh — a shell script that will check out and configure a vulnerable Chromium release.
  • get_pywebsocket.sh — a shell script that will download and patch pywebsocket for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit server.
  • run_pywebsocket.sh — a shell script to start cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit server. You need to separately run cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 serv.py script.

The exploit server runs on two ports: exploit.html is served up by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 websocket server, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second server serves cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image used to trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerability.

Chapter 5. More reliable exploit

At this point we have an exploit that should work sometimes. It creates a lot of "junk" allocations during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap spray, and we’ve made a lot of assumptions about hitting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 correct object types, so let’s evaluate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 probability of a single exploit run succeeding:


Given that one run takes about a minute, this is definitely not a satisfactory result, even with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 virtually unlimited number of retries that we have.

Cookie-based heap grooming

To make our heap spray more reliable we need a way to separate "good" and "bad" 32-byte allocations. As cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reader might have already noticed, precisely manipulating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network process is not a trivial task, especially from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 position of a non-compromised renderer. 

One of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 features related to networking that we haven’t considered yet is HTTP cookies. Somewhat surprisingly, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network process is responsible for not only sending and receiving cookies, but also for storing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m in memory and saving cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m to disk. Since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re exists cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 JavaScript API for cookie operations, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 operations don’t seem to be terribly complex, we might use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 API to build additional heap manipulation primitives.

After some experimentation, we’ve constructed three new heap manipulation primitives:


To be honest, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y are much less straightforward than we originally expected. For example, here’s cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 output of a Frida script that tracks cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 32-byte heap arena state transitions during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 execution of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365
free_slot() method, which, as its name suggests, just appends a new entry to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 32-byte freelist.

No
Operation
Size
Address
1
alloc
24
0x17df6f7c7180
2
alloc
32
0x17df6f7d5bc0
3
free
32
0x17df6f7385c0
4
free
24
0x17df6f738640
5
alloc
24
0x17df6f738640
6
alloc
32
0x17df6f7385c0
7
free
32
0x17df6f760600
8
alloc
24
0x17df6f760600
9
free
24
0x17df6f7c7180
10
alloc
32
0x17df6f7c7180
11
free
32
0x17df6f7c7180
12
free
32
0x17df6f7385c0
13
alloc
24
0x17df6f7385c0
14
free
32
0x17df6f7d5bc0
15
free
24
0x17df6f738640
16
free
24
0x17df6f760600
17
alloc
24
0x17df6f760600
18
alloc
24
0x17df6f738640
19
free
24
0x17df6f7385c0

As you can see, it’s perhaps not cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 simple, clean and tidy primitive we’d like to have, but it does what we need it to!

By carefully integrating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new methods into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit, we can get rid of all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 unwanted allocations in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap spray memory area. The diagram depicting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 updated infoleak spray looks as follows:


Now, in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 worst case, we’ll overwrite cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 LSB of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365
WebSocketFrame pointer with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same value, which will have no effect. This gives us 7/8 instead of 2/8 as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first multiplier of our "reliability" formula. The same applies with regard to ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r parts of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit. As a result, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 above total probability should be bumped up to 0.75 per run.

Again, this method comes with several limitations, for example:
  • The number of cookies a website can have is capped at 180 in Chrome.
  • Every 512th cookie-related operation triggers flushing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 in-memory cookie store to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 disk, which ruins any heap spray.
  • There’s also an automatic flushing every 30 seconds.
  • The cookie store for our website should be in a particular state before each run of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 spray, ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rwise cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 manipulation methods might produce unreliable results.

Luckily, our exploit can be designed to deal with most of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 above restrictions automatically. The heap spray has to be split into tiny chunks, which can be processed separately, though. Because of that, and because we’ve reached cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 limit of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 spray size tied to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 maximum number of connected WebSockets, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 actual exploit ends up to be less reliable. However, paired with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to restart cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network process, it seems to usually take only 2-3 tries before succeeding, which is a whole lot better than cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 previous version.

Chapter 6. Conclusion

The servicification work that Chrome has been doing has had several interesting impacts on exploitation of this kind of bug. First, moving this code into a separate service process (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network service) has had a substantial impact on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 difficulty of finding reliable heap-grooming primitives. This means that even without a proper sandbox, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network service implementation in Chrome means that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network stack is now a slightly harder target than it was before! Service processes implementing a relatively small number of features are inherently harder targets for exploitation than larger monolithic components; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y have a reduced number of available weird-machines, and this reduces attacker choice. We hadn’t really thought about this, so this was an interesting surprise.

Second (and this is definitely a recurring cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365me at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 moment), restarting service processes aids exploitation. For an exploit developer, knowing that you can have as many tries as you need takes a lot of pressure off — you have a lot more freedom to create and use unreliable primitives. This is even more true on platforms that don’t have per-process randomness — we chose Linux for this so that we’d need to construct a stable infoleak — on ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r platforms exploitation may be easier.

Given cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 additional complexity and reliability concerns, it doesn’t seem very likely that this kind of bug is being used by attackers at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 moment. The "traditional" browser exploitation chain of a renderer bug and an OS/kernel privilege escalation is both simpler to write and easier to maintain. However, it’s not unrealistic to think that if those chains start to become scarce, attackers might move to this kind of vulnerability — and we’ve demonstrated that it’s possible to exploit such an issue with a reasonable level of reliability. This means that sandboxing even of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se less-visible attack surfaces is also important to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 overall security of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser.

No comments:

Post a Comment