android沙箱逃逸漏洞,长达数月的努力:揭秘Project Zero团队如何发现Chrome沙箱逃逸漏洞...

}

// 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由std::vector支持,并且watched_handles_在大多数情况下仅包含一个元素,该元素恰好占用16个字节。这意味着,我们可以覆盖Watch指针!

Watch类的大小相对较大,为104个字节,由于tcmalloc的原因,我们只能将大小相似的对象作为目标对象的部分覆盖对象。此外,目标对象在某些偏移处应该包含有效的指针,以使Watch方法的调用不受影响。遗憾的是,网络进程似乎没有包含可以满足上述简单类型混淆要求的类。

但是,我们可以利用Watch是一个引用计数类的事实。我们的思路是,喷射许多Watch size的缓冲区,tcmalloc将其放置在实际Watch对象的旁边,并希望带有被覆盖的最低有效字节的scoped_refptr指向我们的缓冲区之一。缓冲区应该有第一个64位字,也就是伪引用计数器,设置为1,其余设置为0。在这种情况下,对WatcherDispatcher::Close的调用将释放scoped_refptr,这将会导致删除虚假的Watch,析构函数将正常完成,缓冲区将被释放。

如果我们计划将缓冲区发送到攻击者的服务器或返回渲染器进程,那么将会泄露tcmalloc的freelist指针,另一种思路是,如果我们想办法在此期间分配其他内容,则可能会泄露一些有用的指针。因此,我们现在需要尝试在网络进程中创建此类缓冲区,并延迟发送它们,直到发生损坏。

事实证明,Chrome中的网络进程还负责处理WebSocket连接。重要的是,WebSocket是一种低开销的协议,它允许传输二进制数据。如果我们使连接的接收端足够慢,并且发送足够的数据来填充OS套接字发送缓冲区,直到TCPClientSocket::Write变为“asynchronous”(异步)操作为止,随后对WebSocket::send的调用将导致原始帧数据存储为IOBuffer对象,而每个调用仅有两个额外的32字节分配。此外,我们可以通过调整接收方的延迟,来控制缓冲区的生命。

看上去,我们找到了一个近乎完美的堆喷射(Heap Spraying)原语。不过,它有一个缺点——无法释放单个缓冲区。在发送当前批处理或断开连接时,与连接相关的所有帧都会立即释放。我们显然不能为每个喷射对象建立一个WebSocket连接,并且上述每个操作都会在堆中产生很多我们不希望得到的“噪音”。但是,我们先不考虑这些。

下面是该方法的概述:

1a683d527da326859b1cd412b1da32b1.png

遗憾的是,我们很快就证明了,watched_handles_不是太理想的方案。其缺点在于:

1、实际上,有两个flat_map成员,但我们只能使用其中一个成员,因为watched_handles_的损坏会在RemoveWatcherRef虚拟方法调用期间立即引起崩溃。

2、每个WatcherDispatcher分配,都会在我们关注的size类中产生很多不希望得到的“噪音”。

3、对于Watch size类的指针,其LSSB可能有16个(= 256 / GCD(112, 256))可能的值,其中大多数甚至都不指向对象的开头。

尽管我们可以利用这种方法泄露一些数据,但成功率相对偏低。这种方法本身似乎是合理的,但我们必须找到一个更“方便”的容器来进行覆盖。

WebSocket框架

现在,我们可以仔细研究一下如何发送WebSocket框架。

class NET_EXPORT WebSocketChannel {

[...]

std::unique_ptr data_being_sent_;

// Data that is queued up to write after the current write completes.

// Only non-NULL when such data actually exists.

std::unique_ptr data_to_send_next_;

[...]

};

class WebSocketChannel::SendBuffer {

std::vector 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_) {

// Either the link to the 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使用两个单独的SendBuffer对象来存储传出帧。在连接饱和后,新的帧将会进入data_to_send_next_。并且,由于缓冲区由std::vector

如上所述,我们的堆喷射技术为每个所需的分配提供了两个额外的32字节分配。但遗憾的是,WebSocketFrame(我们打算覆盖的指针)大小正好是32个字节。这意味着,除非我们使用其他的堆操作技巧,否则在堆喷射期间生成的所有对象只有1/3属于正确的类型。另一方面,与Watch相比,这个size类中LSB的可选值只有一半,并且指向正确分配的开始部分的概率更大一些。更重要的是,与WatcherDispatcher不同,WebSocket::Send除了调整目标std::vector的大小之外,不会触发任何分配,因此size类的堆喷射会非常简洁。总而言之,我们现在认为data_to_send_next_是最好的目标。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值