chrome sandbox escape case study and plaidctf2020 mojo writeup
字数 1525 2025-08-20 18:18:24

Chrome沙箱逃逸案例研究与PlaidCTF 2020 Mojo漏洞分析

Mojo基础

Mojo简介

Mojo是Chromium项目中的进程间通信(IPC)系统,用于在不同进程间传递消息和接口调用。

核心概念

  1. Message Pipe:一对端点(endpoints),每个端点保存一个传入消息队列,写入一端的消息会传送到另一端,实现双向通信。

  2. Mojom文件:描述一组接口(interfaces),代表强类型的消息集合。

  3. Remote与Receiver

    • Remote:用于发送接口描述的消息
    • Receiver:用于接收接口的消息并分发给具体实现
  4. 接口调用流程

    • 客户端通过Remote发送消息
    • 服务端通过Receiver接收并处理消息
    • 需要响应时,Receiver会通过Remote返回响应

Mojo接口开发流程

定义新接口

  1. 创建.mojom文件
module example.mojom;

interface PingResponder {
  Ping() => (int32 random);
};
  1. 添加构建规则
import("//mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
  sources = [ "ping_responder.mojom" ]
}

创建和使用接口管道

  1. 创建管道
mojo::Remote<example::mojom::PingResponder> ping_responder;
mojo::PendingReceiver<example::mojom::PingResponder> receiver =
    ping_responder.BindNewPipeAndPassReceiver();
  1. 发送消息
ping_responder->Ping(base::BindOnce(&OnPong));
  1. 发送PendingReceiver到Browser进程
RenderFrame* my_frame = GetMyFrame();
my_frame->GetBrowserInterfaceBroker().GetInterface(std::move(receiver));

实现接口

class PingResponderImpl : example::mojom::PingResponder {
 public:
  explicit PingResponderImpl(mojo::PendingReceiver<example::mojom::PingResponder> receiver)
      : receiver_(this, std::move(receiver)) {}

  void Ping(PingCallback callback) override {
    std::move(callback).Run(4);
  }

 private:
  mojo::Receiver<example::mojom::PingResponder> receiver_;
};

Mojo绑定类型

1. Self-owned Receivers

自动管理生命周期的接收器:

mojo::MakeSelfOwnedReceiver(std::make_unique<LoggerImpl>(),
                          logger.BindNewPipeAndPassReceiver());

2. Receiver Sets

用于多个客户端共享同一个实现实例:

class LogManager : public system::mojom::LoggerProvider,
                   public system::mojom::Logger {
 public:
  void GetLogger(mojo::PendingReceiver<Logger> receiver) override {
    logger_receivers_.Add(this, std::move(receiver));
  }
 private:
  mojo::ReceiverSet<system::mojom::Logger> logger_receivers_;
};

3. Remote Sets

维护一组Remote,如事件观察者:

class TableImpl : public db::mojom::Table {
 public:
  void AddListener(mojo::PendingRemote<db::mojom::TableListener> listener) {
    listeners_.Add(std::move(listener));
  }
 private:
  mojo::RemoteSet<db::mojom::Table> listeners_;
};

4. Associated Interfaces

允许在单个消息管道上多路复用多个接口:

interface Foo {
  PassBarRemote(pending_associated_remote<Bar> bar);
  PassBarReceiver(pending_associated_receiver<Bar> bar);
};

JavaScript绑定API

基本用法

<script src="URL/to/mojo_bindings.js"></script>
<script src="URL/to/echo.mojom.js"></script>
<script>
  var echoPtr = new test.echo.mojom.EchoPtr();
  var echoRequest = mojo.makeRequest(echoPtr);
</script>

接口实现示例

function EchoImpl() {}
EchoImpl.prototype.echoInteger = function(value) {
  return Promise.resolve({result: value});
};

var echoServicePtr = new test.echo.mojom.EchoPtr();
var echoServiceRequest = mojo.makeRequest(echoServicePtr);
var echoServiceBinding = new mojo.Binding(test.echo.mojom.Echo,
                                        new EchoImpl(),
                                        echoServiceRequest);

漏洞案例分析

案例1: RenderFrameHost生命周期问题

漏洞原理

  1. InstalledAppProviderImpl通过MakeSelfOwnedReceiver与消息管道绑定生命周期
  2. 但保存了RenderFrameHost的原始指针,未绑定生命周期
  3. 当iframe被移除,RenderFrameHost被释放但InstalledAppProviderImpl仍存在
  4. 后续调用FilterInstalledApps时使用已释放的RenderFrameHost

关键代码

void InstalledAppProviderImpl::FilterInstalledApps(
    std::vector<blink::mojom::RelatedApplicationPtr> related_apps,
    const GURL& manifest_url,
    FilterInstalledAppsCallback callback) {
  if (render_frame_host_->GetProcess()->GetBrowserContext()->IsOffTheRecord()) {
    // UAF可能发生在这里
  }
}

案例2: PlaidCTF 2020 Mojo漏洞

漏洞组合

  1. UAF漏洞

    • 类似案例1,PlaidStoreImpl与消息管道绑定但保存RenderFrameHost原始指针
    • 移除iframe后仍可通过保留的Remote调用方法
  2. OOB读取漏洞

void PlaidStoreImpl::GetData(
    const std::string &key,
    uint32_t count,
    GetDataCallback callback) {
  // ...
  std::vector<uint8_t> result(it->second.begin(), it->second.begin() + count);
  // 无长度检查导致越界读取
}

利用技术

  1. 信息泄露

    • 通过OOB读取泄露虚表地址和chrome基地址
    • 计算ROP gadget位置
  2. 堆喷技术

    • 分配大量与RenderFrameHost大小相同的vector
    • 伪造虚表和ROP链
  3. 利用流程

    • 创建子iframe并获取其RenderFrameHost地址
    • 将Mojo接口Remote传递给父frame
    • 移除子iframe释放RenderFrameHost
    • 通过堆喷占位伪造对象
    • 触发虚函数调用实现控制流劫持

防御措施

  1. 正确管理生命周期

    • 使用WebContentsObserver等机制跟踪对象生命周期
    • 避免保存原始指针
  2. 输入验证

    • 对所有输入参数进行严格验证
    • 特别是数组索引和长度参数
  3. 安全编码实践

    • 使用智能指针管理资源
    • 最小化接口暴露
    • 遵循Chromium安全编码指南

总结

Mojo作为Chromium的核心IPC机制,其安全性直接影响整个浏览器的安全性。开发者需要特别注意:

  1. 接口生命周期的正确管理
  2. 跨进程传递对象的所有权转移
  3. 输入参数的严格验证
  4. 避免在接口实现中保存原始指针

通过分析这些案例,可以更好地理解Mojo接口的安全风险和防御方法。

Chrome沙箱逃逸案例研究与PlaidCTF 2020 Mojo漏洞分析 Mojo基础 Mojo简介 Mojo是Chromium项目中的进程间通信(IPC)系统,用于在不同进程间传递消息和接口调用。 核心概念 Message Pipe :一对端点(endpoints),每个端点保存一个传入消息队列,写入一端的消息会传送到另一端,实现双向通信。 Mojom文件 :描述一组接口(interfaces),代表强类型的消息集合。 Remote与Receiver : Remote:用于发送接口描述的消息 Receiver:用于接收接口的消息并分发给具体实现 接口调用流程 : 客户端通过Remote发送消息 服务端通过Receiver接收并处理消息 需要响应时,Receiver会通过Remote返回响应 Mojo接口开发流程 定义新接口 创建.mojom文件 : 添加构建规则 : 创建和使用接口管道 创建管道 : 发送消息 : 发送PendingReceiver到Browser进程 : 实现接口 Mojo绑定类型 1. Self-owned Receivers 自动管理生命周期的接收器: 2. Receiver Sets 用于多个客户端共享同一个实现实例: 3. Remote Sets 维护一组Remote,如事件观察者: 4. Associated Interfaces 允许在单个消息管道上多路复用多个接口: JavaScript绑定API 基本用法 接口实现示例 漏洞案例分析 案例1: RenderFrameHost生命周期问题 漏洞原理 : InstalledAppProviderImpl 通过 MakeSelfOwnedReceiver 与消息管道绑定生命周期 但保存了 RenderFrameHost 的原始指针,未绑定生命周期 当iframe被移除, RenderFrameHost 被释放但 InstalledAppProviderImpl 仍存在 后续调用 FilterInstalledApps 时使用已释放的 RenderFrameHost 关键代码 : 案例2: PlaidCTF 2020 Mojo漏洞 漏洞组合 : UAF漏洞 : 类似案例1, PlaidStoreImpl 与消息管道绑定但保存 RenderFrameHost 原始指针 移除iframe后仍可通过保留的Remote调用方法 OOB读取漏洞 : 利用技术 信息泄露 : 通过OOB读取泄露虚表地址和chrome基地址 计算ROP gadget位置 堆喷技术 : 分配大量与 RenderFrameHost 大小相同的vector 伪造虚表和ROP链 利用流程 : 创建子iframe并获取其 RenderFrameHost 地址 将Mojo接口Remote传递给父frame 移除子iframe释放 RenderFrameHost 通过堆喷占位伪造对象 触发虚函数调用实现控制流劫持 防御措施 正确管理生命周期 : 使用 WebContentsObserver 等机制跟踪对象生命周期 避免保存原始指针 输入验证 : 对所有输入参数进行严格验证 特别是数组索引和长度参数 安全编码实践 : 使用智能指针管理资源 最小化接口暴露 遵循Chromium安全编码指南 总结 Mojo作为Chromium的核心IPC机制,其安全性直接影响整个浏览器的安全性。开发者需要特别注意: 接口生命周期的正确管理 跨进程传递对象的所有权转移 输入参数的严格验证 避免在接口实现中保存原始指针 通过分析这些案例,可以更好地理解Mojo接口的安全风险和防御方法。